0.0.5-alpha.5.fix.1

This commit is contained in:
2026-09-25 08:14:57 +02:00
parent f03f8640d4
commit 81c7a720f5
9 changed files with 864 additions and 124 deletions

View File

@@ -1,51 +1,111 @@
<!-- file: docs/validation/003-RUST_EXPORT_COMPLETENESS.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Audit facultatif de façade Rust et export completeness
# Audit facultatif de façade Rust et canonicalisation intra-crate
## Responsabilité
`scripts/audit_rust_export_completeness.py` contrôle un contrat Rust optionnel plus strict que les garanties du compilateur : les éléments publics durables d'un module fichier doivent être exposés par une façade explicite au crate-root, et le code de la même crate doit employer cette façade lorsqu'un réexport correspondant existe.
`scripts/audit_rust_export_completeness.py` contrôle un contrat Rust optionnel plus strict que les garanties du compilateur et de Clippy : une crate qui adopte cette convention possède une façade crate-root explicite pour ses items partagés, et toute consommation intra-crate d'un item possédant cette identité canonique passe par `crate::Item`.
Le propriétaire normatif reste [`../rules/510-RUST_CARGO.md`](../rules/510-RUST_CARGO.md). Le script ne définit pas une architecture à lui seul et ne doit être adopté que par un projet qui choisit ce contrat de façade.
Le propriétaire normatif reste [`../rules/510-RUST_CARGO.md`](../rules/510-RUST_CARGO.md). Le script ne définit pas une architecture à lui seul ; il matérialise uniquement le contrat strict lorsqu'un projet choisit de l'adopter.
L'audit émet seulement deux familles de diagnostics :
L'audit émet deux familles de diagnostics :
```text
RUST-API-201
élément `pub`, ou élément `pub(crate)` clairement consommé hors de son module fichier,
sans réexport explicite correspondant au crate-root
item `pub` d'un module fichier sans réexport explicite au crate-root,
ou item `pub(crate)` clairement consommé hors de son module sans réexport crate-root
RUST-IMPORT-201
chemin interne long `crate::module::Item` alors que le crate-root expose déjà `crate::Item`
référence intra-crate non canonique à un item possédant une identité crate-root ;
la forme attendue est `crate::Item`
```
Il ne vérifie pas les règles générales de style, les lignes vides, l'ordre des constantes, la rustdoc, les lints Cargo/Clippy, la sûreté FFI ou le contenu métier.
`RUST-IMPORT-201` ne se limite donc pas à `crate::module::Item`. Il couvre notamment :
```text
Item
self::Item
super::Item
module::Item
crate::module::Item
```
lorsque ces formes désignent un item partagé déjà exposé comme `crate::Item`.
La règle s'applique également **dans le module qui déclare l'item**. Par exemple :
```rust
pub struct Runtime;
impl Runtime {
pub fn new() -> Runtime {
return Runtime;
}
}
```
n'est pas canonique après réexport de `Runtime`. Le contrat strict attend :
```rust
pub struct Runtime;
impl crate::Runtime {
pub fn new() -> crate::Runtime {
return crate::Runtime;
}
}
```
Le même principe vaut pour une fonction partagée appelée depuis son module propriétaire : `crate::build()` est canonique, `build()` ne l'est pas.
Les lignes de déclaration de l'item et les lignes de réexport qui construisent la façade sont naturellement exemptées. Un item strictement privé n'appartient pas à ce contrat ; dans un test unitaire attaché à son module parent, `super::PrivateItem` reste une forme locale légitime.
## Pourquoi Cargo/Clippy ne suffisent pas
Le compilateur vérifie la résolution et la visibilité des noms ; il ne possède pas la convention « tout accès partagé passe par `crate::Item` ». Clippy peut signaler certains imports ou réexports inutilisés, mais une seule consommation correcte de `crate::Item` peut suffire à rendre le réexport utilisé tandis que d'autres références continuent d'employer un nom nu ou un chemin interne.
L'audit fournit donc une preuve distincte : **uniformité de la façade et des chemins intra-crate**, pas simple compilabilité.
## Cibles Cargo et graphes de modules
La `version: 2` ne confond plus automatiquement tous les fichiers d'un package Cargo dans une seule crate. Un package peut contenir simultanément une bibliothèque `src/lib.rs` et un binaire `src/main.rs` ; ces deux cibles sont compilées comme des crates distinctes et `crate::Item` n'y désigne pas la même racine.
L'audit découvre les packages récursivement puis traite séparément les cibles standard/explicites `lib` et `bin`. Pour chaque cible, il suit les modules fichiers déclarés par `mod name;` et les modules `#[path = "..."]`, ce qui couvre notamment les `unit_tests/` attachés utilisés dans le corpus KSP/Games sans englober les tests d'intégration Cargo sous `tests/`, qui sont des crates externes.
Cette distinction évite de considérer à tort `owner_crate::Item` utilisé dans un binaire comme une violation de la bibliothèque du même package.
## Provenance et correction du corpus
KSP `0.3.16` et Games `0.3.5` contiennent un `audit_rust_export_completeness.py` dont le corps est identique à l'exception de sa révision de fichier. Cette convergence justifie l'extraction du concept, mais pas une copie littérale.
KSP `0.3.16` et Games `0.3.5` contiennent un `audit_rust_export_completeness.py` historiquement identique à l'exception de sa révision. Cette convergence justifie l'extraction du concept, mais pas une copie littérale.
Le script historique découvre uniquement `crates/*/Cargo.toml`. Cette hypothèse couvre les `22` crates KSP situées directement sous `crates/`, mais elle ne couvre aucune des `23` crates Games organisées sous des sous-familles telles que `crates/apps/`, `crates/common/` ou `crates/engines/`. Un résultat historique `0 candidate(s)` dans Games ne constitue donc pas une preuve que ses crates avaient réellement été inspectées par cet audit.
Deux limites historiques ont été identifiées :
La variante PGR corrige ce point en découvrant récursivement les packages Cargo possédant `src/lib.rs` ou `src/main.rs`, tout en ignorant les arbres générés usuels. Elle gère également correctement un module fichier `mod.rs` et les réexports explicites simples, groupés ou aliasés.
1. la découverte `crates/*/Cargo.toml` couvrait les `22` packages KSP directement sous `crates/`, mais aucune des `23` crates Games imbriquées sous `crates/apps/`, `crates/common/`, `crates/engines/`, etc. ;
2. la canonicalisation détectait surtout `crate::module::Item` et quelques cas particuliers de tests, mais pas les références ordinaires par nom nu dans les modules de production, y compris le module propriétaire.
La réconciliation générateur réalisée pendant `0.0.5-alpha.5` donne avec la nouvelle découverte :
La première version PGR de `alpha.5` corrigeait seulement la découverte récursive et le chemin interne long. La revue opérateur après sa gate a explicitement révélé la seconde lacune ; `alpha.5` est donc conservé `FAIL` et `alpha.5.fix.1` étend `RUST-IMPORT-201` au contrat réellement attendu.
## Mesure de réconciliation après le fix
Avec la `version: 2`, en mode `--report-only --summary-only`, les baselines autoritaires de la réconciliation donnent :
```text
KSP 0.3.16 22 crate(s) 0 candidate(s)
Games 0.3.5 23 crate(s) 2 candidate(s)
KSP 0.3.16
22 package(s)
1525 candidate(s)
RUST-IMPORT-201: 1525
Games 0.3.5
23 package(s)
181 candidate(s)
RUST-API-201: 2
RUST-IMPORT-201: 179
```
Les deux candidats Games sont des constantes `pub(crate)` du smoke WebTransport navigateur consommées depuis d'autres modules sans façade crate-root. Games n'étant pas encore gouverné par PGR, ces signaux ne sont pas qualifiés de défauts rétroactifs ; ils montrent simplement ce qu'une adoption future de ce contrat devrait décider ou corriger.
Ces nombres représentent des **lignes candidates**, pas des déclarations distinctes. Ils sont beaucoup plus élevés que la première mesure `0 / 2` parce que le contrat corrige précisément le blind spot signalé : les références nues déjà répandues dans les modules propriétaires et consumers deviennent enfin visibles.
## Ce qui n'est pas repris du script historique
PGR n'importe pas les diagnostics historiques liés aux tests unitaires séparés (`super::...`, références non qualifiées) : leur validité dépend du layout et du contrat local des modules de test, et la baseline Games ne prouve pas une convergence opérationnelle sur ce point.
PGR n'importe pas non plus le contrôle d'un `crate::Symbol` non résolu. Le compilateur Rust est déjà propriétaire de la résolution de noms ; dupliquer cette erreur dans un audit Python n'apporte pas une preuve supplémentaire proportionnée.
L'audit PGR reste donc monotâche : façade crate-root et canonicalisation des chemins pour les seuls symboles qu'il sait relier explicitement.
KSP et Games n'étant pas encore gouvernés par cette version de PGR, ces résultats ne sont pas qualifiés de défauts rétroactifs et ne déclenchent aucune migration dans `0.0.5`. Ils constituent un inventaire d'adoption future. Pour un projet existant, l'usage initial en `--report-only` est donc particulièrement important.
## Adoption
@@ -64,20 +124,22 @@ Commande canonique :
python3 -B scripts/audit_rust_export_completeness.py --root .
```
Le code de sortie est `0` lorsque le corpus est propre, `1` lorsqu'au moins un candidat est trouvé et `2` lorsque la racine est invalide, illisible ou ne contient aucune crate Rust détectable. `--report-only` permet une phase d'inventaire sans bloquer la gate ; `--summary-only` réduit la sortie aux comptes.
Le code de sortie est `0` lorsque le corpus est propre, `1` lorsqu'au moins un candidat est trouvé et `2` lorsque la racine est invalide, illisible ou ne contient aucun package Rust détectable. `--report-only` permet un inventaire sans bloquer la gate ; `--summary-only` réduit la sortie aux comptes.
Une adoption progressive peut donc commencer par :
Une adoption progressive d'un projet existant devrait commencer par :
```bash
python3 -B scripts/audit_rust_export_completeness.py --root . --report-only
python3 -B scripts/audit_rust_export_completeness.py --root . --report-only --summary-only
```
puis devenir bloquante une fois les exceptions ou écarts locaux explicitement résolus.
puis inventorier/corriger les familles d'écarts avant de rendre l'audit bloquant. Une nouvelle crate créée sous le contrat peut au contraire l'activer immédiatement.
## Limites
L'analyse est lexicale et non un parseur Rust complet. Elle vise les déclarations `pub`/`pub(crate)` au niveau racine de modules fichiers et les réexports explicites du crate-root. Les macros générant des items, attributs élaborés, modules inline complexes, `#[path]` non standard et formes de réexport imbriquées au-delà du contrat testé peuvent nécessiter une revue humaine ou les outils Rust du projet.
L'analyse reste lexicale et ne remplace pas un parseur Rust complet. Elle vise les déclarations module-level `pub`/`pub(crate)`, les réexports explicites et les références dans le graphe de modules d'une cible. Les macros générant des items, modules inline complexes, `include!`, formes de `#[path]` atypiques et réexports élaborés peuvent nécessiter une revue humaine.
Un élément `pub(crate)` n'est exigé à la façade que lorsqu'une consommation externe au fichier est clairement détectée par un chemin `crate::module::Item` ou `crate::Item`. L'absence de diagnostic ne prouve donc pas toute la sémantique de visibilité de la crate.
Pour réduire les faux positifs, une fonction partagée est reconnue comme référence lorsqu'elle apparaît sous une forme d'appel directe ; l'usage d'une fonction comme valeur de premier ordre peut donc échapper à cette version. Les chemins explicitement qualifiés par une crate externe et les appels de méthodes `.name()` ne sont pas assimilés à un item local de même nom.
L'audit ne remplace ni `cargo check`, ni Clippy, ni rustdoc. Il complète uniquement une convention architecturale explicitement choisie.
Un item `pub(crate)` qui n'est pas clairement consommé hors de son module n'est pas forcé au crate-root par `RUST-API-201`; sa visibilité peut nécessiter une revue séparée. Dès qu'un réexport crate-root existe, ses références détectées sont toutefois soumises à `RUST-IMPORT-201`, y compris dans son module de déclaration.
L'audit ne remplace ni `cargo check`, ni Clippy, ni rustdoc. Il complète ces outils avec une convention architecturale qu'ils ne possèdent pas.