🦀 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>
This commit is contained in:
cloudion-labo
2026-08-16 01:07:31 +00:00
commit 2556698dd3
27 changed files with 6518 additions and 0 deletions

91
bion-tsoinlog/README.md Normal file
View File

@@ -0,0 +1,91 @@
# bion-tsoinlog — le journal append-only rejouable
## Quoi
La **primitive de la machine à tsoins** : un journal d'événements sur disque où
l'on ne fait qu'**appender** (jamais modifier, jamais effacer) et que l'on peut
**rejouer** — en entier ou par tranche `[from, to)`. Chaque événement = un
`topic` (UTF-8) + un `payload` (octets opaques), et reçoit un numéro de séquence
`Seq` strictement croissant.
- **std-only**, zéro dépendance, zéro `unsafe`.
- Format binaire v1 minuscule et documenté : `magic "TSOINLG1"` puis
`len(u32 LE) · topic_len(u16 LE) · topic · payload · crc32(u32 LE)`.
- **CRC-32 (IEEE) implémenté maison** (table générée à la compilation, vecteur
canonique `"123456789" → 0xCBF43926` testé) — pas de lib, build-your-own.
- **Récupération après crash** : à l'ouverture, la queue tronquée ou au CRC faux
est détectée et retaillée ; l'historique valide survit toujours (testé en
tronquant et en corrompant le fichier à la main).
- `fsync` configurable (`Sync::Always` / `Sync::Never` + `sync()` manuel).
- Index en mémoire (offset de chaque record) reconstruit au scan d'ouverture →
`iter_from(seq)` et `replay` démarrent en seek direct, pas de re-scan.
## Pourquoi
C'est la structure au cœur de Kafka, des WAL de bases de données, de l'event
sourcing, de git — et de la machine à tsoins : **enregistrer le réel dans
l'ordre, pouvoir le revivre**. Le complément exact de `tsoin-codec`
(`~/xerboxion-rt/crates/tsoin-codec`) : le codec transforme un contenu en
coordonnée de Babel, le log mémorise durablement la *séquence* des événements.
On appende volontiers des tsoins-de-fil comme payloads ; le log, lui, ne
présuppose rien sur le contenu.
## Exemple
```rust
use bion_tsoinlog::{TsoinLog, Seq, Sync};
let mut log = TsoinLog::open("journal.tsoinlog")?; // Sync::Never par défaut
// ou : TsoinLog::open_with("journal.tsoinlog", Sync::Always)? // durable à chaque append
let s0 = log.append("capteur/temp", b"21.5")?; // → Seq(0)
let s1 = log.append("bus/emit", b"{\"topic\":\"leds\"}")?; // → Seq(1)
// Tout relire :
for rec in log.iter()? {
let rec = rec?;
println!("#{} [{}] {} octets", rec.seq.0, rec.topic, rec.payload.len());
}
// Rejouer une tranche [1, 2) (from inclus, to exclu, comme un Range) :
log.replay(Seq(1), Seq(2), |rec| { /* ré-appliquer l'événement */ })?;
# std::io::Result::Ok(())
```
## API publique (stable — rétrocompatibilité éternelle)
| Élément | Rôle |
|---|---|
| `TsoinLog::open(path)` / `open_with(path, Sync)` | ouvre/crée + scan + réparation |
| `append(topic, payload) -> io::Result<Seq>` | appende un événement |
| `iter()` / `iter_from(Seq)` | itère (instantané cohérent, seek O(1)) |
| `replay(from, to, f) -> io::Result<u64>` | rejoue `[from, to)`, retourne le compte |
| `sync()` / `set_sync(Sync)` | contrôle du `fsync` |
| `len()` / `is_empty()` / `next_seq()` | état du journal |
| `Seq(u64)` / `Record { seq, topic, payload }` / `Sync` | types de données |
| `crc32(&[u8]) -> u32` / `MAGIC` / `MAX_TOPIC_LEN` | briques exposées |
## Build-your-own-x correspondants
Dans [build-your-own-x](https://github.com/codecrafters-io/build-your-own-x) :
- **Build your own Database** — le log est un *write-ahead log* (WAL) minimal ;
- **Build your own Git** — un historique append-only adressé par position ;
- le CRC-32 maison est la brique commune à zip/gzip/PNG/Ethernet (« build your
own checksum » depuis la division polynomiale dans GF(2)).
## Comment l'optimiser (l'invitation au fork)
Le format v1 est volontairement le plus simple qui soit correct. Pistes, dans
l'ordre de rentabilité, **sans jamais casser la lecture des fichiers v1** :
1. **`Sync::EveryN(n)` / group commit** — amortir le `fsync` sur n appends ;
2. **index persistant** (fichier `.idx` side-car, régénérable) — ouverture O(1)
sur les très gros journaux au lieu du scan complet ;
3. **segments + compaction** — découper en fichiers de taille bornée, archiver
ou fusionner les vieux segments (le chemin vers Kafka) ;
4. **mmap en lecture** — itération zéro-copie ;
5. **CRC vectorisé** (slicing-by-8, ou `crc32` matériel SSE4.2) — même résultat,
~10× plus vite ;
6. **compression des payloads** — brancher `tsoin-codec` : appender la
coordonnée de Babel au lieu des octets bruts.