Comment je peux faire confiance aux nombres ?

Quatre crates Rust, une spec TLA+ vérifiée par TLC, quatre property tests gate avant chaque release.

Cette page ne dit pas « ces nombres sont vrais ». Elle dit ce qui aurait dû casser avant qu'un nombre sorte du pipeline. La différence est tout.

Pipeline — d'un .dbn.zst à une rangée de Parquet

graph LR
    DBN[".dbn.zst<br/>databento MBP-10"] --> ADAPTER[salim-dbn-adapter]
    ADAPTER --> PIPELINE[salim-pipeline]
    PIPELINE --> CORE[salim-core<br/>RV, BV, J,<br/>classifier]
    CORE --> EXPORT[salim-export<br/>Parquet schema V1]
    EXPORT --> PARQUET[".parquet<br/>WindowVerdict rows"]
    PARQUET --> PYTHON[Python plots<br/>research/plots/]

Lecture de gauche à droite : un fichier compressé en entrée, une rangée typée en sortie. Aucune boucle, aucun retour en arrière, aucun écrivain vers le disque source. Le frontière Rust → Python est posée au Parquet — pas de Python dans le hot path (ADR-0002 §D-1).

Les quatre crates

Chaque coupure passe deux signaux de la règle D-1 (ADR-0002 §D-1) : cycle de versionnement disjoint, surface de dépendances disjointe, frontière de confiance, ou public consumer attendu. Le salim-cli n'est pas un crate mais le binaire qui les assemble.

CrateResponsabilitéPath
salim-coreStats RV/BV/J, classifier state machine, types domainresearch/crates/salim-core/
salim-dbn-adapterDécodage DBN \(\to\) domain types (frontière read-only)research/crates/salim-dbn-adapter/
salim-pipelineWorker tick \(\to\) WindowVerdict, orchestrationresearch/crates/salim-pipeline/
salim-exportSchéma Parquet V1, writer atomiqueresearch/crates/salim-export/
+ salim-cli (binary)Entry-point CLI, parse flags, lance pipelineresearch/bins/salim-cli/

Le découpage n'est pas une élégance — c'est un test compile-time. salim-dbn-adapter ne peut pas accidentellement écrire dans le domain : il ne dépend pas de parquet. salim-core ne peut pas accidentellement lire un .dbn.zst : il ne dépend pas de dbn. Le typeur Rust applique la frontière à chaque cargo build.

Validation TLA+ — avant de lancer 11M ticks de calcul

TLA+ n'est pas une preuve que l'algo est juste. C'est une preuve que le graphe d'exécution est terminal et sans deadlock. Sur cette pré-étude on a deux specs model-checkées par TLC.

Spec DAG du pipelinespecs/MCSalimPipelineDAG.tla. Modèle : 1 jour \(\times\) 2 instruments (ARM, IONQ, symétrie permutation) \(\times\) 3 timestamps, avec 1 tick d'auction. Invariants vérifiés : ordre des étapes (LoadDbn → ComputeStat → Classify → ExportRow → Done), pas de skip, pas de cycle, INV_RO_COSMONDATA (aucun write vers le disque source). La spec adverse MCSalimPipelineDAGAttack.tla injecte volontairement une action AttemptWriteCosmonData pour vérifier que la spec elle-même détecte la violation — un test de la spec, pas du code.

Spec flotte de designspecs/MCDesignFleet.tla. Modèle : panel 5 personas \(\times\) 4 topics (réduit pour rester model-checkable, les 3 autres couverts par monotonie). Invariants : coverage des topics \((\geq\) 2 personas indépendantes par topic), panel non-vide, terminal cohérent. C'est la spec qui a fait passer la délibération delib-20260513-54ed de « panel d'opinions » à « panel vérifié ».

Pourquoi TLA+ avant 11M ticks — un deadlock découvert après 8 heures de calcul coûte 8 heures de calcul. TLC découvre le même deadlock en quelques secondes sur un modèle borné. Une fois la spec verte, on lance.

Gates property-tests — quatre invariants algébriques

Une PR qui casse l'un des quatre est bloquée. Ces tests tournent avant chaque release (ADR-0003 §5).

PropertyCe qu'elle testeRéférence
prop_rv_positiveRV(t,τ) ≥ 0 pour returns iid (Brownien)ADR-0003 §5
prop_bv_le_rv_no_jumpBV ≤ RV quand pas de saut injectéidem
prop_j_in_unit_intervalJ ∈ [-ε, 1] à fenêtre finieidem
prop_classifier_monotonic_dwelldwell strict croissant tant qu'aucune transitionidem

Ces 4 tests tournent avant chaque release. Une PR qui casse l'un des quatre est bloquée.

Ce ne sont pas des tests unitaires. Ce sont des propriétés algébriques : elles posent une relation que le calcul doit respecter pour toute entrée valide, et le framework cherche des contre-exemples. Si BV ≤ RV n'est pas tenu sur 10 000 trajectoires sans saut, ce n'est pas ce test qui a échoué — c'est l'algorithme. À ce moment-là on ne recompile pas le test, on revient au code.

Données — read-only sur le disque source

Les .dbn.zst vivent sur /Volumes/CosmonData/MaQI-Data/databento/. Le code ne les écrit jamais, ne les déplace jamais, ne les renomme jamais. La frontière est testée à la fois par le découpage en crates (salim-dbn-adapter n'a pas de writer) et par la spec TLA+ adverse (INV_RO_COSMONDATA). Les Parquet dérivés sont gitignored sous research/.cache/ ; les figures dérivées sous research/plots/out/. C'est l'invariant DNA D3 de la galaxie, et il est appliqué à trois niveaux : architecture (crates), preuve formelle (TLC), discipline opérationnelle (gitignore).

Pour ton cas

Pour Salim : si tu utilises un signal en prod, tu veux savoir (a) que le pipeline ne deadlock pas (TLA+), (b) que les invariants algébriques tiennent (property-tests), et (c) que les nombres ne dérivent pas silencieusement entre deux versions (commit hash dans chaque Parquet row). C'est exactement ce qu'on a câblé.

Le hash de commit dans le frontmatter de cette page (51b89624c625) est le même format que celui inscrit dans la métadonnée de chaque rangée Parquet exportée. Une figure regénérée six mois plus tard se relie à un état exact du code — pas à « mai 2026 ».

Limites


Adversary : structural-decay — risque que la stack évolue sans que cette page suive. Antidote V1 : commit_hash dans le frontmatter (51b89624c625). Antidote V2 (advisory, knuth) : INV-DRIFT-DETECTION — hook de pré-release qui regénère les pages-pilier si un crate salim-* a été touché.

Voir aussi : realized-variance \(\cdot\) bipower-and-jumps \(\cdot\) regime-classifier \(\cdot\) pivot-j-based \(\cdot\) cases-index \(\cdot\) charter