dupehell 0.5.1

Synthetic multi-domain dataset generator for record linkage benchmarking — Educational Use Only
Documentation
# FIXES.md — Plan de remédiation consolidé

**Source** : rustgod audit (Waves 1-9) → rustgod-qc (triage, 114 VALIDÉ) → rustgod-fix (solutions)
**Méthode** : 9 subagents fixes, 3490+ lignes de propositions — consolidé, dédupliqué, ordonné par sévérité.
**Aucun code modifié** — snippets à appliquer manuellement.
**Total : 114 fixes** (11🔴, 27🟠, 56🟡, 20⚪)

---

## 🔴 Priorité critique (11 fixes)

| # | Finding | Fichier:LN | Trick | Solution |
|---|---------|-----------|-------|----------|
| 1 | W3-F1 | `pipeline.rs:500-504,1105-1154` | IO-07 | Écriture directe Parquet ZSTD(3), supprimer phase IPC intermédiaire (économie ~400 Go I/O) |
| 2 | W3-F2 | `noise/mod.rs:29-37` | MEM-10 | `get_chars_into(buf: &mut Vec<char>)` réutilisable entre lignes (élimine 12M allocs/batch) |
| 3 | W1-F1 | `buf_gen.rs:26-38` | MEM-10 | Ajouter `buf: &mut Vec<u8>` à `build_string_array`, recycler le buffer entre colonnes |
| 4 | W1-F2 | `fast_template.rs:627-1090` | DSA-04 | 14 templates → `StringDictionaryArray<i32>` (gain mémoire 3-6×) |
| 5 | W7-F2 | `fast_template.rs:25-33` | CMP-04/DSA-03 | Ajouter `debug_assert!` + `log::warn!` sur débordement `write_zpad` |
| 6 | W7-F4 | `pool_lookup.rs:17-33` | — | Remplacer colonne `""` silencieuse par `log::warn!` ou `Err` |
| 7 | W9-F1 | `.github/workflows/ci.yml, pypi.yml` | — | Épingler 6 actions GitHub par SHA + commentaire version |
| 8 | W8-F1 | `src/pipeline.rs` | — | Ajouter module `#[cfg(test)]` avec tests d'intégration pipeline complet |
| 9 | W8-F2 | `src/schema.rs`, `src/difficulty.rs` | — | Tests unitaires pour `build_pipeline_config()` et `estimate_difficulty()` |
| 10 | W7-F15 | `pipeline.rs:132-137` | — | Augmenter taille hash ou ajouter compteur entité dans master_id (collision 0.78%) |
| 11 | W7-F11 | `pipeline.rs:990-996` | — | Passer espace HN ID de 7 à 9+ digits (évite collision ~40% à 100K HN) |

## 🟠 Priorité haute (27 fixes)

| # | Finding | Fichier:LN | Trick | Solution |
|---|---------|-----------|-------|----------|
| 13 | W1-F3 | `buf_gen.rs:77` | MEM-04 | `Vec::with_capacity(len)` + `set_len` au lieu de `vec![0u8; len]` |
| 14 | W1-F4 | `buf_gen.rs:52` | MEM-04 | Idem F-3 pour `buf_digits` |
| 15 | W1-F5 | `buf_gen.rs:49-69` | MEM-10 | Passer `&mut Vec<u8>` entre appels `buf_digits` |
| 16 | W1-F6 | `pipeline.rs:685,795-804` | MEM-09 | Rematérialiser slices HN via `pick_rows` (cas multi-slice) |
| 17 | W1-F7 | `entity_gen.rs:206` | MEM-10 | Itérer sur `StringArray` au lieu de `Vec<String>` |
| 18 | W1-F8 | `pool_lookup.rs:17-41` | MEM-09 | Pooler `StringBuilder` entre colonnes même pool |
| 19 | W2-F3 | `pipeline.rs:860-864` | MEM-09 | Remplacer `s.spawn(move ||)` par rayon `par_iter()` (emprunt sans clone) |
| 20 | W2-F6 | `pipeline.rs:1072-1105` | CONC-07 | Paralléliser GT finish + conversion Parquet via `std::thread::scope` |
| 21 | W2-F8 | `graph_gen.rs:172-176` | MEM-10 | `builder.clear()` au lieu de `= StringBuilder::new()` dans flush |
| 22 | W3-F3 | `typos.rs, visual.rs, names.rs` | DSA-06 | 4 petits `LazyLock<HashMap>` → `match` arms / tableau trié |
| 23 | W3-F4 | `companies.rs:28-31` | MEM-10/ZCO-02 | `s.strip_suffix(suff)` zéro alloc si pas de match |
| 24 | W3-F5 | `dates.rs:70,90,113` | MEM-10/IO-02 | `[&str; 3]` stack + `write!()` au lieu de Vec + join |
| 25 | W3-F6 | `addresses.rs:108-113` | MEM-10 | Itération directe `filter().count()` + 1 scan position |
| 26 | W4-F4 | *(tous fichiers)* | TCH-04 | Ajouter `#[cold]` sur 4 fonctions d'erreur/cold path |
| 27 | W5-F1 | `Cargo.toml:40-44` | TCH-01 | Profil `release-cli` avec `panic = "abort"` dédié au binaire |
| 28 | W5-F3 | `main.rs:220-260` | CLI-ergo | Ajouter progression utilisateur (`info!` périodique / indicatif) |
| 29 | W7-F8 | `fast_template.rs:772-781` | — | Étendre canary email override à tout nom contenant "email" |
| 30 | W8-F3 | `.github/workflows/ci.yml:5` | — | Matrix OS (ubuntu, windows, macos) pour tests |
| 31 | W8-F4 | `.github/workflows/ci.yml:13` | — | `cargo test --release` en CI |
| 32 | W8-F5 | `.github/workflows/ci.yml` | — | Test feature Python en CI (`cargo build --features python`) |
| 33 | W8-F6 | `.github/workflows` | — | Ajouter `cargo-deny` / `cargo-audit` en CI |
| 34 | W8-F7 | `main.rs:221` | — | `env!("CARGO_PKG_VERSION")` au lieu de "v0.4" hardcodé |
| 35 | W9-F2 | `Cargo.toml:35` | — | Migration pyo3 0.23 → 0.29 (plan progressive) |
| 36 | W9-F3 | `.github/workflows` | — | Ajouter `cargo deny check` en CI |
| 37 | W9-F5 | `fast_template.rs:81,98,99,146,147,207` | — | 6 `.unwrap()` → `.expect("pool 'X' not found")` |
| 38 | W9-F6 | `pipeline.rs:883,802,804,1418` | — | 4 `.expect()` → `?` (propagation d'erreur) |
| 39 | W9-F7 | `main.rs:151-164` | — | Estimation mémoire avant génération + warning si proche limite |

## 🟡 Priorité modérée (56 fixes)

| # | Finding | Fichier:LN | Trick | Solution |
|---|---------|-----------|-------|----------|
| 40 | W1-F9 | `fast_template.rs:251,379,444` | MEM-09 | Closure directe dans `buf_digits` au lieu de `Vec<u64>` 500K |
| 41 | W1-F10 | `fast_template.rs:1051-1064` | MEM-10 | `write_zpad` dans buffer réutilisable au lieu de `to_string()` |
| 42 | W1-F11 | `pool_lookup.rs:36` | MEM-01 | Calcul capacité basé sur avg_len réel |
| 43 | W1-F12 | `column_gen.rs:258-271` | MEM-09 | Bit-flip sur buffer Arrow au lieu de rebuild null mask |
| 44 | W1-F13 | `pipeline.rs:627` | MEM-10 | `write!` dans buffer au lieu de `format!` |
| 45 | W1-F14 | `pipeline.rs:708` | MEM-10 | `clear()` + `extend()` au lieu de nouvelle `Vec<String>` |
| 46 | W1-F16 | `buf_gen.rs:26,88`, `fast_template.rs:25` | TCH-04 | Ajouter `#[inline]` sur hot path |
| 47 | W3-F10 | `visual.rs:210-241` | CMP-04 | Rebuild masqué O(n) au lieu de `remove(p)` O(n²) |
| 48 | W3-F13 | `extra.rs:89-95` | MEM-10 | `String::with_capacity()` + `push_str`/`push` (1 alloc) |
| 49 | W3-F14 | `identifiers.rs:199-213` | CMP-01 | Tableau stack `[usize; 9]` + `char_indices` (1 scan) |
| 50 | W3-F15 | `pool_lookup.rs:44`-`column_gen.rs:283` | MEM-10 | Passer `&norm_name` à `strip_prefix` |
| 51 | W4-F6 | `entity_gen.rs:228-484` | ANT-01 | 12 `.unwrap()` → `.expect("invariant: ...")` |
| 52 | W4-F9 | `fast_template.rs:98,99,146,147` | ANT-03 | `.unwrap()` → `.expect("pool 'X' not found")` |
| 53 | W4-F11 | `pipeline.rs:1092` | DIAG-02 | Supprimer `_t_gt_compute = 0.0` morte |
| 54 | W4-F12 | `pipeline.rs:1093` | DIAG-02 | Renommer `_t_gt_write` → `_t_gt_compute_write` |
| 55 | W5-F6 | `Cargo.toml:22` | TCH-02 | `default-features = false, features = ["ipc"]` pour arrow |
| 56 | W5-F7 | `Cargo.toml:30` | TCH-02 | `default-features = false, features = ["arrow", "zstd"]` pour parquet |
| 57 | W5-F8 | `Cargo.toml` | TCH-02 | Ajouter `.cargo/config.toml` avec `target-cpu=native` |
| 58 | W5-F11 | `main.rs:220` | CLI-ergo | `env!("CARGO_PKG_VERSION")` |
| 59 | W5-F12 | `main.rs:151-164` | IO-07 | Valider `size` avant `Context::new` |
| 60 | W5-F14 | `pool_lookup.rs:60-67` | MEM-01 | Normaliser une fois, éviter double allocation |
| 61 | W5-F17 | `legal_form.json` vs `legal_forms.json` | POOL-redun | Renommer → `legal_form_en.json` / `legal_form_fr.json` |
| 62 | W5-F18 | `company.json` vs `companies.json` | POOL-redun | Renommer → `company_names.json` / `company_formal.json` |
| 63 | W5-F20 | `python/__init__.py:198` | CLI-ergo | Kwargs ou `GenerateConfig` struct |
| 64 | W6-F4 | `pipeline.rs:685` | MEM-09 | Rematérialiser compact via `pick_rows` pour hn_slices |
| 65 | W6-F7 | `schema.rs:253,300,305` + `difficulty.rs:255,289` | MEM-01 | `with_capacity(known_len)` aux 5 sites |
| 66 | W6-F12 | `schema.rs:272-278` | MEM-01 | `HashMap::with_capacity(plans.len())` |
| 67 | W7-F12 | `gt.rs:236-241` | — | Stocker indices `usize` au lieu de `String` dans cluster_map |
| 68 | W7-F14 | `hn_common.rs:305-308` | — | Valider colonnes HN avant génération dans `difficulty.rs` |
| 69 | W8-F9 | `src/noise/mod.rs:41` | — | Tests noise avec vérification de transformation réelle |
| 70 | W8-F10 | `src/canary.rs` + modules | — | Tests unitaires basiques |
| 71 | W8-F11 | `src/pipeline.rs:204` | — | Tests `noise_type_targets_column()` |
| 72 | W8-F12 | `src/main.rs` + `src/lib.rs` | — | Tests validation args CLI |
| 73 | W8-F13 | `src/entity_gen.rs:97` | — | Tests `apply_column_conditions` (eq, ne, gt, set_null...) |
| 74 | W8-F14 | `.github/workflows/pypi.yml` | — | Workflow pré-release vers TestPyPI |
| 75 | W8-F15 | `.github/workflows/pypi.yml:39` | — | Migrer OIDC trusted publishing |
| 76 | W8-F16 | `.github/workflows/pypi.yml` | — | Ajouter `--sdist` à maturin-build |
| 77 | W8-F17 | `.github/workflows/ci.yml` | — | Auditwheel (Linux) + delocate (macOS) |
| 78 | W8-F18 | `.github/workflows/static.yml:40` | — | Limiter upload Pages à `path: './book'` |
| 79 | W8-F19 | `README.md:54-62` | — | Mettre à jour tableau options CLI |
| 80 | W8-F20 | `README.md:136` | — | Corriger décompte pools (133 réels) |
| 81 | W8-F21 | `python/dupehell/models.py:84` | — | Aligner modèle Python `HnSchema` avec Rust |
| 82 | W8-F22 | `src/pipeline.rs:155-178` | — | Remplacer commentaires "2026-07 session" |
| 83 | W8-F23 | `src/gt.rs:78` | — | Docstring `GtAccumulator::new()` |
| 84 | W8-F24 | `src/difficulty.rs:179-204` | — | Docstring `EntityDifficulty` |
| 85 | W8-F25 | `src/fast_template.rs:20` | — | Docstring `TemplateFn` |
| 86 | W9-F8 | `.github/workflows/pypi.yml:40` | — | Migrer trusted publishing OIDC |
| 87 | W9-F9 | `.github/workflows/pypi.yml` | — | Aligner versions upload/download artifact |
| 88 | W9-F10 | `.github/workflows/pypi.yml` | — | `--sdist` + test-PyPI |
| 89 | W9-F11 | `Cargo.toml` | — | Ajouter `rust-version = "1.85"` |
| 90 | W9-F12 | `.github/workflows/ci.yml` | — | Ajouter `--locked` à cargo build/test |
| 91 | W9-F13 | `Cargo.toml:24,30` | — | Désactiver codecs Parquet inutilisés |
| 92 | W9-F14 | `Cargo.toml:24,30` | — | Désactiver features arrow inutilisées |
| 93 | W9-F19 | `pipeline.rs:480-482` | — | Handler SIGINT/SIGTERM avec cleanup |
| 94 | W9-F20 | `pipeline.rs:1152; gt.rs:264` | — | Cleanup fichiers via `Drop` ou `defer` |

## ⚪ Priorité faible (20 fixes)

| # | Finding | Fichier:LN | Trick | Solution |
|---|---------|-----------|-------|----------|
| 95 | W1-F17 | `entity_gen.rs:471`, `context.rs:34`, `pipeline.rs:1316` | MEM-01 | `with_capacity(taille_connue)` |
| 96 | W1-F18 | `buf_gen.rs:35` | ZCO-06 | `from_utf8_unchecked` + commentaire SAFETY |
| 97 | W1-F19 | `column_gen.rs:283` | MEM-10 | Normaliser une fois et réutiliser |
| 98 | W2-F21 | `pipeline.rs:837-854` | CONC-04 | Ajouter `AtomicU64` par noise type |
| 99 | W2-F22 | `gt.rs:202-204` | CONC-04 | Migrer compteurs vers `AtomicU64` (si finish parallélisé) |
| 100 | W3-F20 | `fast_template.rs:769` | DSA-08 | Slice trié `binary_search_by_key` au lieu de `HashMap::get` |
| 101 | W3-F21 | `schema.rs:253,280,300` | MEM-01 | `with_capacity(schema.entities.len())` |
| 102 | W4-F22 | `pool_lookup.rs:21-26` | MEM-10 | `StringBuilder::new()` au lieu de `n*4` surdimensionné |
| 103 | W5-F23 | `Cargo.toml` | TCH-03 | Script PGO `scripts/pgo.sh` |
| 104 | W5-F25 | `main.rs:32` | CLI-ergo | `PossibleValuesParser` avec liste domaines |
| 105 | W7-F16 | `schema.rs:127-141` | — | Remplacer `DefaultHasher` par hash stable (SHA256 tronqué) |
| 106 | W7-F17 | `pipeline.rs:750-1047` | — | Assertion `dataset rows == GT rows` après génération |
| 107 | W7-F19 | `pipeline.rs:308` / `difficulty.rs:12` | — | Factoriser `contains_any` dans utilitaire commun |
| 108 | W8-F26 | `src/noise/extra.rs:7-10` | — | Ajouter `//!` module-docs aux modules manquants |
| 109 | W8-F27 | `.github/workflows/pypi.yml` | — | Cache pip/maturin dans PyPI workflow |
| 110 | W8-F28 | `.github/workflows/ci.yml` | — | Ajouter `cargo bench` optionnel |
| 111 | W8-F29 | `.github/workflows/ci.yml` | — | Code coverage via cargo-llvm-cov |
| 112 | W8-F30 | `.github/workflows/ci.yml` | — | `concurrency` group dans CI |
| 113 | W8-F31 | `.github/` | — | PR template + branch protection config |
| 114 | W8-F32 | `src/pipeline.rs:112,123` | — | `.unwrap()` → `.expect("contexte explicatif")` |

---

## Statistiques

| Sévérité | Nombre | % du backlog |
|----------|:------:|:------------:|
| 🔴 Critique | 11 | 10% |
| 🟠 Haute | 27 | 23% |
| 🟡 Modérée | 56 | 49% |
| ⚪ Faible | 20 | 18% |
| **Total** | **114** | **100%** |

## Traçabilité

Chaque fix ci-dessus est détaillé dans les rapports individuels :
- `rustgod/fix-wave-1.md` (18 fixes — alloc mémoire)
- `rustgod/fix-wave-2.md` (5+3 fixes — concurrence + architecture)
- `rustgod/fix-wave-3.md` (12 fixes — I/O, zero-copy, compute)
- `rustgod/fix-wave-4.md` (6 fixes — sûreté, robustesse)
- `rustgod/fix-wave-5.md` (13 fixes — build, Python, déploiement)
- `rustgod/fix-wave-7.md` (10 fixes — qualité données, GT)
- `rustgod/fix-wave-8a.md` (16 fixes — tests, CI, partie 1)
- `rustgod/fix-wave-8b.md` (16 fixes — tests, CI, partie 2)
- `rustgod/fix-wave-9.md` (15 fixes — sécurité, supply chain)

**Rapports QC** (source des verdicts) :
- `rustgod/QC-SUMMARY.md` (synthèse complète)
- `rustgod/WAVE-*-CONSOLIDATED.md` (9 fichiers annotés)

---

*Aucun fichier `.rs` modifié — généré par `rustgod-fix`, le 2026-07-17.*