0.0.5-alpha.5

This commit is contained in:
2026-09-25 00:36:24 +02:00
parent defc9ca4b5
commit f03f8640d4
12 changed files with 849 additions and 15 deletions

View File

@@ -1 +1 @@
0.0.5-alpha.4 0.0.5-alpha.5

205
deltas/0.0.5/alpha.5.md Normal file
View File

@@ -0,0 +1,205 @@
<!-- file: deltas/0.0.5/alpha.5.md -->
<!-- version: 1 -->
# Delta 0.0.5-alpha.5 — audit Rust de façade/export completeness
## Baseline et version
Baseline attendue : commit opérateur complet de `0.0.5-alpha.4`, réalisé après application de `history/0.0.5/alpha.4.md` avec statut `PASS`.
Le hash de ce futur commit n'est pas anticipé.
Version avant application :
```text
0.0.5-alpha.4
```
Version après application :
```text
0.0.5-alpha.5
```
## Rôle de la tranche
`alpha.5` extrait un audit Rust facultatif réellement réutilisable à partir du corpus KSP/Games, mais corrige avant généralisation une limite découverte dans le script historique : Games organise ses crates sous plusieurs niveaux et n'était pas réellement couvert par le glob `crates/*/Cargo.toml`.
La tranche reste limitée à la façade crate-root et aux chemins same-crate canoniques. Elle ne copie pas `audit_rust_general_rules.py`, ne commence pas Tauri et ne transforme pas PGR en workspace Rust.
## Scope
REPLACE :
```text
VERSION
docs/000-README.md
docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md
docs/rules/510-RUST_CARGO.md
docs/skeletons/002-RUST_CARGO_WORKSPACE.md
docs/studies/001-KSP_GAMES_RECONCILIATION.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
docs/validation/003-RUST_EXPORT_COMPLETENESS.md
scripts/audit_rust_export_completeness.py
tests/test_rust_export_completeness.py
deltas/0.0.5/alpha.5.md
```
DELETE : aucun.
Le history `alpha.4` appartient au commit baseline et n'est pas inclus dans l'archive du présent delta.
## Décisions matérialisées
### Audit monotâche
Le nouveau script possède exactement deux responsabilités :
```text
RUST-API-201
façade crate-root absente pour un élément public, ou crate-public clairement partagé
RUST-IMPORT-201
chemin `crate::module::Item` alors qu'un réexport explicite fournit déjà `crate::Item`
```
Les conventions des tests unitaires séparés du script historique ne sont pas reprises. Le contrôle d'un symbole `crate::X` inexistant n'est pas repris non plus, car le compilateur Rust possède déjà la résolution de noms.
### Découverte des crates
La découverte des `Cargo.toml` devient récursive et accepte les crates imbriquées. Les arbres générés usuels (`target`, `build`, `builds`, `dist`, `node_modules`, etc.) restent ignorés.
Le module fichier `foo/mod.rs` est interprété comme `foo`, et les réexports crate-root simples, groupés ou aliasés couverts par les tests sont reconnus.
### Adoption facultative
Le contrat général Rust reste un `SHOULD`. Un projet existant peut commencer avec `--report-only`, traiter les écarts ou exceptions locales, puis rendre l'audit bloquant seulement après décision explicite.
L'audit est documenté dans `docs/validation/003-RUST_EXPORT_COMPLETENESS.md` et référencé par le skeleton Rust/Cargo sans devenir obligatoire.
### Mesure de réconciliation
La prévalidation générateur sur les baselines autoritaires de `alpha.1` observe :
```text
KSP 0.3.16 22 crate(s) 0 candidate(s)
Games 0.3.5 23 crate(s) 2 candidate(s)
```
Le résultat Games antérieur `0 candidate(s)` provenait d'une découverte de crates incomplète. Les deux candidats actuels ne sont pas traités comme des défauts historiques, car Games n'est pas encore gouverné par PGR.
## Validation opérateur
Après application du history `PASS` de `alpha.4`, commit/push de cette tranche, puis application du présent delta :
```bash
test "$(cat VERSION)" = '0.0.5-alpha.5'
test -f history/0.0.5/alpha.4.md
test -f deltas/0.0.5/alpha.5.md
test -f scripts/audit_rust_export_completeness.py
test -f tests/test_rust_export_completeness.py
test -f docs/validation/003-RUST_EXPORT_COMPLETENESS.md
test ! -e history/0.0.5/alpha.5.md
grep -A2 '^## Statut$' history/0.0.5/alpha.4.md | grep -Fx 'PASS'
grep -Fx '# version: 1' scripts/audit_rust_export_completeness.py
grep -Fx '# version: 1' tests/test_rust_export_completeness.py
grep -Fx '<!-- version: 1 -->' docs/validation/003-RUST_EXPORT_COMPLETENESS.md
grep -Fx '<!-- version: 3 -->' docs/rules/510-RUST_CARGO.md
grep -Fx '<!-- version: 2 -->' docs/skeletons/002-RUST_CARGO_WORKSPACE.md
grep -Fx '<!-- version: 11 -->' docs/validation/001-VALIDATION_MATRIX.md
python3 -B -m unittest discover -s tests -p 'test_rust_export_completeness.py' -v
python3 -B -m unittest discover -s tests -v
python3 -B scripts/audit_markdown_headers.py \
README.md RULES.md ROADMAP.md CHANGELOG.md docs deltas history prompts
python3 -B scripts/audit_markdown_tables.py \
README.md RULES.md ROADMAP.md CHANGELOG.md docs deltas history prompts
python3 -B scripts/audit_markdown_links.py \
README.md RULES.md ROADMAP.md CHANGELOG.md docs deltas history prompts
python3 -B - <<'PY'
from pathlib import Path
import re
history = Path('history/0.0.5/alpha.4.md').read_text(encoding='utf-8')
assert re.search(r'## Statut\s+PASS\s', history)
script = Path('scripts/audit_rust_export_completeness.py').read_text(encoding='utf-8')
for phrase in (
'def crate_roots(',
'root.rglob("Cargo.toml")',
'RUST-API-201',
'RUST-IMPORT-201',
'--report-only',
'--summary-only',
):
assert phrase in script, phrase
for forbidden in ('RUST-IMPORT-202', 'RUST-IMPORT-203', 'RUST-IMPORT-204', 'RUST-IMPORT-205'):
assert forbidden not in script, forbidden
validation = Path('docs/validation/003-RUST_EXPORT_COMPLETENESS.md').read_text(encoding='utf-8')
for phrase in (
'22` crates KSP',
'23` crates Games',
'0 candidate(s)',
'2 candidate(s)',
'ne sont pas qualifiés de défauts rétroactifs',
):
assert phrase in validation, phrase
plan = Path('docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md').read_text(encoding='utf-8')
assert 'KSP = 0 candidate(s) / 22 crates' in plan
assert 'Games = 2 candidate(s) / 23 crates' in plan
print('0.0.5 alpha.5 Rust façade audit: clean (recursive crate discovery, narrow ownership, regression coverage)')
PY
git diff --check
git add VERSION docs/000-README.md \
docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md \
docs/rules/510-RUST_CARGO.md \
docs/skeletons/002-RUST_CARGO_WORKSPACE.md \
docs/studies/001-KSP_GAMES_RECONCILIATION.md \
docs/validation/001-VALIDATION_MATRIX.md \
docs/validation/003-RUST_EXPORT_COMPLETENESS.md \
scripts/audit_rust_export_completeness.py \
tests/test_rust_export_completeness.py \
deltas/0.0.5/alpha.5.md
git diff --cached --check
git status --short
```
La sortie Git attendue avant ajout du history `alpha.5` est exactement :
```text
M VERSION
A deltas/0.0.5/alpha.5.md
M docs/000-README.md
M docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md
M docs/rules/510-RUST_CARGO.md
M docs/skeletons/002-RUST_CARGO_WORKSPACE.md
M docs/studies/001-KSP_GAMES_RECONCILIATION.md
M docs/validation/001-VALIDATION_MATRIX.md
A docs/validation/003-RUST_EXPORT_COMPLETENESS.md
A scripts/audit_rust_export_completeness.py
A tests/test_rust_export_completeness.py
```
## Transition
### PASS
Créer/appliquer `history/0.0.5/alpha.5.md`, committer/pousser `0.0.5-alpha.5`, puis poursuivre à `0.0.5-alpha.6` pour le premier profil framework Tauri.
### FAIL
Rester dans `0.0.5-alpha.5.fix.M` si le défaut concerne l'audit, ses tests, sa documentation ou l'intégration Rust/Cargo. Ne pas absorber le profil Tauri dans un fix.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md --> <!-- file: docs/000-README.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Documentation PGR # Documentation PGR
@@ -26,6 +26,7 @@ Les templates techniques copiables sous `templates/` restent séparés des instr
- [`studies/001-KSP_GAMES_RECONCILIATION.md`](studies/001-KSP_GAMES_RECONCILIATION.md) — inventaire comparatif des baselines KSP/Games et décisions de généralisation, maintien local ou différé. - [`studies/001-KSP_GAMES_RECONCILIATION.md`](studies/001-KSP_GAMES_RECONCILIATION.md) — inventaire comparatif des baselines KSP/Games et décisions de généralisation, maintien local ou différé.
- [`templates/004-FILE_FAMILY_CONTRACTS.md`](templates/004-FILE_FAMILY_CONTRACTS.md) — pattern facultatif pour expliciter les responsabilités et règles de modification de familles de fichiers locales lorsque la complexité le justifie. - [`templates/004-FILE_FAMILY_CONTRACTS.md`](templates/004-FILE_FAMILY_CONTRACTS.md) — pattern facultatif pour expliciter les responsabilités et règles de modification de familles de fichiers locales lorsque la complexité le justifie.
- [`templates/005-RELEASE_PLAN.md`](templates/005-RELEASE_PLAN.md) — pattern de plan de release avec clôture/transition explicites et revue d'horizon proportionnée. - [`templates/005-RELEASE_PLAN.md`](templates/005-RELEASE_PLAN.md) — pattern de plan de release avec clôture/transition explicites et revue d'horizon proportionnée.
- [`validation/003-RUST_EXPORT_COMPLETENESS.md`](validation/003-RUST_EXPORT_COMPLETENESS.md) — contrat, provenance et limites de l'audit Rust facultatif de façade crate-root.
## Points d'entrée de 0.0.4 ## Points d'entrée de 0.0.4
@@ -36,4 +37,4 @@ Les templates techniques copiables sous `templates/` restent séparés des instr
- [`skeletons/002-RUST_CARGO_WORKSPACE.md`](skeletons/002-RUST_CARGO_WORKSPACE.md) — specialization Rust/Cargo workspace composable avec la fondation et les cinq templates existants ; - [`skeletons/002-RUST_CARGO_WORKSPACE.md`](skeletons/002-RUST_CARGO_WORKSPACE.md) — specialization Rust/Cargo workspace composable avec la fondation et les cinq templates existants ;
- [`templates/001-RUST_CARGO_TOOLING.md`](templates/001-RUST_CARGO_TOOLING.md) et [`templates/002-CARGO_MANIFESTS.md`](templates/002-CARGO_MANIFESTS.md) — adaptation des cinq fichiers Rust/Cargo copiables ; - [`templates/001-RUST_CARGO_TOOLING.md`](templates/001-RUST_CARGO_TOOLING.md) et [`templates/002-CARGO_MANIFESTS.md`](templates/002-CARGO_MANIFESTS.md) — adaptation des cinq fichiers Rust/Cargo copiables ;
- [`templates/003-NEW_SESSION_PROMPT.md`](templates/003-NEW_SESSION_PROMPT.md) — spécification du message manuel après une stable publiée ; - [`templates/003-NEW_SESSION_PROMPT.md`](templates/003-NEW_SESSION_PROMPT.md) — spécification du message manuel après une stable publiée ;
- [`validation/001-VALIDATION_MATRIX.md`](validation/001-VALIDATION_MATRIX.md) et [`validation/002-RUST_STYLE_AUDITS.md`](validation/002-RUST_STYLE_AUDITS.md) — gates PGR et limites des audits de style Rust facultatifs. - [`validation/001-VALIDATION_MATRIX.md`](validation/001-VALIDATION_MATRIX.md), [`validation/002-RUST_STYLE_AUDITS.md`](validation/002-RUST_STYLE_AUDITS.md) et [`validation/003-RUST_EXPORT_COMPLETENESS.md`](validation/003-RUST_EXPORT_COMPLETENESS.md) — gates PGR et limites des audits Rust facultatifs.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md --> <!-- file: docs/plans/005-V0_0_5_RECONCILIATION_PLAN.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Plan 0.0.5 — réconciliation réelle avec KSP et sasedev-games # Plan 0.0.5 — réconciliation réelle avec KSP et sasedev-games
@@ -84,6 +84,10 @@ Cette tranche ne commence pas l'audit Rust ni le profil Tauri.
Extraire le noyau réellement commun de `audit_rust_export_completeness.py`, l'aligner avec `510-RUST_CARGO.md`, documenter son caractère facultatif et ses limites, et ajouter des tests positifs/négatifs. Extraire le noyau réellement commun de `audit_rust_export_completeness.py`, l'aligner avec `510-RUST_CARGO.md`, documenter son caractère facultatif et ses limites, et ajouter des tests positifs/négatifs.
La réconciliation a révélé que le script historique ne découvrait que `crates/*/Cargo.toml` : il couvrait les `22` crates KSP mais aucune des `23` crates Games imbriquées sous des sous-familles. La variante PGR doit donc corriger la découverte récursive avant toute généralisation. Elle doit également abandonner les sous-règles historiques de tests séparés et la duplication de résolution de noms déjà possédée par le compilateur.
Mesure de référence avec le scope PGR retenu : `KSP = 0 candidate(s) / 22 crates`, `Games = 2 candidate(s) / 23 crates`. Les deux signaux Games sont une observation de réconciliation, pas une non-conformité rétroactive.
Cette tranche ne réimporte pas `audit_rust_general_rules.py` en bloc. Toute règle déjà possédée par Rustfmt, Clippy, le compilateur ou les audits PGR existants reste chez son owner actuel. Cette tranche ne réimporte pas `audit_rust_general_rules.py` en bloc. Toute règle déjà possédée par Rustfmt, Clippy, le compilateur ou les audits PGR existants reste chez son owner actuel.
### alpha.6 — premier profil framework : Tauri ### alpha.6 — premier profil framework : Tauri

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/510-RUST_CARGO.md --> <!-- file: docs/rules/510-RUST_CARGO.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Profil Rust / Cargo # Profil Rust / Cargo
@@ -192,4 +192,8 @@ Lorsqu'une règle Rust devient objectivement automatisable dans un projet ou ske
Un éventuel wrapper d'orchestration MAY appeler plusieurs audits, mais chaque audit propriétaire doit conserver une responsabilité identifiable et rester read-only. Un éventuel wrapper d'orchestration MAY appeler plusieurs audits, mais chaque audit propriétaire doit conserver une responsabilité identifiable et rester read-only.
Les deux audits de style disponibles sont documentés dans [`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md). Ils restent optionnels et prennent un corpus Rust explicitement sélectionné ; le dépôt PGR lui-même ne contient pas de workspace Rust sur lequel une telle gate fournirait une preuve réelle. Les deux audits de style disponibles sont documentés dans [`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md). Ils restent optionnels et prennent un corpus Rust explicitement sélectionné.
[`../validation/003-RUST_EXPORT_COMPLETENESS.md`](../validation/003-RUST_EXPORT_COMPLETENESS.md) documente séparément un audit facultatif de façade crate-root. Lorsqu'un projet choisit ce contrat strict, les éléments `pub` des modules fichiers et les éléments `pub(crate)` clairement partagés hors de leur module SHOULD être réexportés explicitement au crate-root ; lorsqu'un tel réexport existe, les chemins internes SHOULD employer `crate::Item` plutôt que `crate::module::Item`.
Cet audit de façade ne rend pas la règle obligatoire pour tous les projets Rust et ne remplace pas le compilateur. Le dépôt PGR lui-même ne contient pas de workspace Rust sur lequel une telle gate fournirait une preuve runtime ou Cargo réelle.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/skeletons/002-RUST_CARGO_WORKSPACE.md --> <!-- file: docs/skeletons/002-RUST_CARGO_WORKSPACE.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Skeleton — Rust/Cargo workspace # Skeleton — Rust/Cargo workspace
@@ -57,7 +57,7 @@ templates/rust-cargo/Cargo.toml
templates/rust-cargo/crates/project-core-lib/Cargo.toml templates/rust-cargo/crates/project-core-lib/Cargo.toml
``` ```
[`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md) est seulement une option supplémentaire. Les scripts `audit_rust_blank_lines.py`, `audit_rust_const_blocks.py` et `_rust_audit_common.py` ne sont copiés que si le projet choisit explicitement les conventions correspondantes et sait quels fichiers Rust doivent être audités. [`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md) et [`../validation/003-RUST_EXPORT_COMPLETENESS.md`](../validation/003-RUST_EXPORT_COMPLETENESS.md) sont seulement des options supplémentaires. Les scripts Rust PGR ne sont copiés que si le projet choisit explicitement les conventions correspondantes. Le helper `_rust_audit_common.py` peut être partagé entre ces audits.
## Décisions destinataire avant copie ## Décisions destinataire avant copie
@@ -72,7 +72,7 @@ Avant de considérer la specialization comme instanciée, résoudre explicitemen
7. les niveaux de lints hérités, les éventuelles exceptions locales et tout besoin réel d'`unsafe` dans un scope étroit ; 7. les niveaux de lints hérités, les éventuelles exceptions locales et tout besoin réel d'`unsafe` dans un scope étroit ;
8. les paramètres `rustfmt.toml` et `clippy.toml` à conserver ou adapter ; 8. les paramètres `rustfmt.toml` et `clippy.toml` à conserver ou adapter ;
9. l'emplacement des artefacts Cargo et l'identifiant unique utilisé dans `.cargo/config.toml` si cette politique externe au dépôt est retenue ; 9. l'emplacement des artefacts Cargo et l'identifiant unique utilisé dans `.cargo/config.toml` si cette politique externe au dépôt est retenue ;
10. les audits de style Rust facultatifs réellement adoptés et leur corpus exact ; 10. les audits Rust facultatifs réellement adoptés, dont éventuellement le contrat de façade crate-root, et leur scope exact ;
11. les validations Cargo réellement nécessaires au code et aux manifests créés. 11. les validations Cargo réellement nécessaires au code et aux manifests créés.
Les valeurs `0.1.0`, `2024`, `project-name`, `project-core-lib`, les seuils Clippy et les chemins de build présents dans les templates sont des exemples ou baselines à revoir. Ils ne deviennent jamais des valeurs universelles par simple copie. Les valeurs `0.1.0`, `2024`, `project-name`, `project-core-lib`, les seuils Clippy et les chemins de build présents dans les templates sont des exemples ou baselines à revoir. Ils ne deviennent jamais des valeurs universelles par simple copie.
@@ -127,11 +127,11 @@ L'édition de `rustfmt.toml` doit rester cohérente avec le workspace. Les seuil
Décider explicitement si `Cargo.lock` est versionné selon le type de produit, le mode de publication et le besoin de reproductibilité. La présence ou l'absence du lockfile dans un projet source historique ne constitue pas une règle suffisante pour le nouveau workspace. Décider explicitement si `Cargo.lock` est versionné selon le type de produit, le mode de publication et le besoin de reproductibilité. La présence ou l'absence du lockfile dans un projet source historique ne constitue pas une règle suffisante pour le nouveau workspace.
### Audits de style facultatifs ### Audits Rust facultatifs
Si le projet adopte les conventions documentées dans [`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md), copier les trois scripts concernés et adapter leurs headers à leurs chemins locaux. Si le projet adopte les conventions documentées dans [`../validation/002-RUST_STYLE_AUDITS.md`](../validation/002-RUST_STYLE_AUDITS.md), copier les scripts de style concernés et adapter leurs headers à leurs chemins locaux. La sélection des fichiers doit rester explicite. En particulier, l'audit des blocs de constantes ne doit jamais réordonner une table de protocole, une séquence de migrations ou toute autre structure dont l'ordre possède une sémantique propre.
La sélection des fichiers doit rester explicite. En particulier, l'audit des blocs de constantes ne doit jamais réordonner une table de protocole, une séquence de migrations ou toute autre structure dont l'ordre possède une sémantique propre. Si le projet adopte le contrat de façade décrit dans [`../validation/003-RUST_EXPORT_COMPLETENESS.md`](../validation/003-RUST_EXPORT_COMPLETENESS.md), copier `audit_rust_export_completeness.py` avec `_rust_audit_common.py`. Une adoption d'un projet existant SHOULD commencer en `--report-only`, résoudre les écarts ou exceptions locales, puis seulement rendre l'audit bloquant.
Ne pas copier ces scripts uniquement parce qu'ils existent dans PGR. Ne pas copier ces scripts uniquement parce qu'ils existent dans PGR.
@@ -150,7 +150,7 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings
Ajouter des tests ciblés pour les crates qui possèdent déjà un comportement à vérifier. Un `cargo build` n'est nécessaire que lorsqu'un artefact de build doit réellement être produit. Ajouter des tests ciblés pour les crates qui possèdent déjà un comportement à vérifier. Un `cargo build` n'est nécessaire que lorsqu'un artefact de build doit réellement être produit.
Si les audits de style facultatifs ont été sélectionnés, les exécuter après le formatage sur les seuls chemins concernés. Si des audits Rust facultatifs ont été sélectionnés, exécuter les audits de style après formatage sur les seuls chemins concernés et l'audit de façade sur la racine du projet une fois son contrat explicitement adopté.
La validation doit aussi confirmer : La validation doit aussi confirmer :

View File

@@ -1,5 +1,5 @@
<!-- file: docs/studies/001-KSP_GAMES_RECONCILIATION.md --> <!-- file: docs/studies/001-KSP_GAMES_RECONCILIATION.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Étude de réconciliation PGR / KSP / sasedev-games # Étude de réconciliation PGR / KSP / sasedev-games
@@ -133,6 +133,12 @@ Décision : classer ce contrôle `COMMON`, mais ne pas copier le script tel quel
3. fournir des tests positifs/négatifs ciblés et documenter les limites du parsing lexical ; 3. fournir des tests positifs/négatifs ciblés et documenter les limites du parsing lexical ;
4. éviter de réintroduire les règles déjà possédées par Rustfmt, Clippy ou les deux audits de style PGR existants. 4. éviter de réintroduire les règles déjà possédées par Rustfmt, Clippy ou les deux audits de style PGR existants.
#### Mesure alpha.5
La relecture du script historique révèle une limite importante : sa découverte `crates/*/Cargo.toml` couvre les `22` crates KSP placées directement sous `crates/`, mais aucune des `23` crates Games imbriquées sous `crates/apps`, `crates/common`, `crates/engines`, etc. Le code des deux scripts est bien commun, mais le résultat historique Games `0 candidate(s)` n'était pas une preuve d'audit des crates réelles.
La variante PGR corrige la découverte récursive et réduit le scope aux deux propriétés effectivement possédées par `510-RUST_CARGO.md` : complétude de la façade crate-root et canonicalisation d'un chemin interne lorsqu'un réexport explicite existe. Avec cette variante, KSP donne `0 candidate(s)` sur `22` crates et Games `2 candidate(s)` sur `23` crates. Ces deux signaux Games ne sont pas des non-conformités rétroactives ; ils deviennent des points de décision lors d'une future adoption PGR.
Le grand `audit_rust_general_rules.py` des deux corpus reste `DEFER` comme source d'extraction, pas comme script à importer. Il mélange plusieurs responsabilités : headers, rustdoc, imports, structure de façade, blancs, constantes et règles locales ; Games y ajoute une exception FFI Android étroite. PGR doit continuer à extraire des audits monotâches seulement lorsqu'une propriété commune apporte une preuve non redondante. Le grand `audit_rust_general_rules.py` des deux corpus reste `DEFER` comme source d'extraction, pas comme script à importer. Il mélange plusieurs responsabilités : headers, rustdoc, imports, structure de façade, blancs, constantes et règles locales ; Games y ajoute une exception FFI Android étroite. PGR doit continuer à extraire des audits monotâches seulement lorsqu'une propriété commune apporte une preuve non redondante.
L'orchestrateur `audit_rust_workspace_rules.py` est un pattern d'ergonomie utile mais ne possède aucune règle. PGR peut documenter comment composer des audits sélectionnés dans un projet destinataire sans fournir un lanceur universel qui supposerait les mêmes scripts partout. L'orchestrateur `audit_rust_workspace_rules.py` est un pattern d'ergonomie utile mais ne possède aucune règle. PGR peut documenter comment composer des audits sélectionnés dans un projet destinataire sans fournir un lanceur universel qui supposerait les mêmes scripts partout.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_MATRIX.md --> <!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
<!-- version: 10 --> <!-- version: 11 -->
# Matrice de validation PGR # Matrice de validation PGR
@@ -41,6 +41,8 @@ PGR préfère plusieurs audits simples avec une responsabilité identifiable à
Leur disponibilité ne transforme pas PGR en workspace Rust et ne rend pas automatiquement l'ordre alphabétique applicable à chaque fichier d'un projet. Lors d'une adoption, le projet propriétaire sélectionne ses chemins après `cargo fmt` et applique sa propre gate ; Rustfmt et Clippy conservent les contrôles qu'ils possèdent. Leur disponibilité ne transforme pas PGR en workspace Rust et ne rend pas automatiquement l'ordre alphabétique applicable à chaque fichier d'un projet. Lors d'une adoption, le projet propriétaire sélectionne ses chemins après `cargo fmt` et applique sa propre gate ; Rustfmt et Clippy conservent les contrôles qu'ils possèdent.
[`003-RUST_EXPORT_COMPLETENESS.md`](003-RUST_EXPORT_COMPLETENESS.md) décrit séparément `audit_rust_export_completeness.py`, audit facultatif de façade crate-root. Sa validation ciblée dans PGR est `python3 -B -m unittest discover -s tests -p test_rust_export_completeness.py -v`. L'audit est destiné aux projets Rust qui choisissent explicitement ce contrat ; PGR n'exécute pas ce script sur son propre dépôt comme gate, car il ne constitue pas lui-même un workspace Rust.
## Politique Eclipse actuelle ## Politique Eclipse actuelle
Les métadonnées Eclipse spécifiques à une machine restent ignorées, notamment `.project`, `.classpath` et les préférences non partagées. Les métadonnées Eclipse spécifiques à une machine restent ignorées, notamment `.project`, `.classpath` et les préférences non partagées.

View File

@@ -0,0 +1,83 @@
<!-- file: docs/validation/003-RUST_EXPORT_COMPLETENESS.md -->
<!-- version: 1 -->
# Audit facultatif de façade Rust et export completeness
## 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.
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.
L'audit émet seulement 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
RUST-IMPORT-201
chemin interne long `crate::module::Item` alors que le crate-root expose déjà `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.
## 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.
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.
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.
La réconciliation générateur réalisée pendant `0.0.5-alpha.5` donne avec la nouvelle découverte :
```text
KSP 0.3.16 22 crate(s) 0 candidate(s)
Games 0.3.5 23 crate(s) 2 candidate(s)
```
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.
## 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.
## Adoption
Pour adopter cette convention dans un projet Rust, copier :
```text
scripts/_rust_audit_common.py
scripts/audit_rust_export_completeness.py
```
puis adapter les headers de fichiers au dépôt destinataire.
Commande canonique :
```bash
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.
Une adoption progressive peut donc commencer par :
```bash
python3 -B scripts/audit_rust_export_completeness.py --root . --report-only
```
puis devenir bloquante une fois les exceptions ou écarts locaux explicitement résolus.
## 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.
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.
L'audit ne remplace ni `cargo check`, ni Clippy, ni rustdoc. Il complète uniquement une convention architecturale explicitement choisie.

105
history/0.0.5/alpha.5.md Normal file
View File

@@ -0,0 +1,105 @@
<!-- file: history/0.0.5/alpha.5.md -->
<!-- version: 1 -->
# History 0.0.5-alpha.5 — audit Rust de façade/export completeness
## Statut
FAIL
La gate mécanique de `0.0.5-alpha.5` exécutée le `2026-09-25` est propre, mais la revue fonctionnelle qui suit révèle que le contrat livré est incomplet par rapport à la convention Rust que PGR doit pouvoir porter : l'audit détecte `crate::module::Item`, mais ne détecte pas les références non qualifiées ou autrement non canoniques à un item partagé déjà exposé au crate-root, notamment dans son propre module de déclaration.
Le statut global reste donc `FAIL`. Les résultats mécaniques ne sont pas annulés ; ils prouvent que l'implémentation livrée fonctionne selon son contrat écrit, tandis que la revue révèle que ce contrat est trop faible.
## Baseline et version
La tranche part du commit opérateur de `0.0.5-alpha.4` :
```text
defc9ca 0.0.5-alpha.4
```
Le push vers `origin/master` a réussi avant l'application de `alpha.5`.
La version contrôlée après application du delta est :
```text
0.0.5-alpha.5
```
`history/0.0.5/alpha.4.md`, `deltas/0.0.5/alpha.5.md`, `scripts/audit_rust_export_completeness.py`, `tests/test_rust_export_completeness.py` et `docs/validation/003-RUST_EXPORT_COMPLETENESS.md` sont présents. `history/0.0.5/alpha.5.md` était absent pendant la gate.
## Validation opérateur observée
L'opérateur a fourni les sorties du `2026-09-25` depuis `~/Projects/pgr`, à `00:03:49`–`00:08:36`.
Les contrôles de présence, statut et révision ont réussi. La suite ciblée a exécuté `9` tests, tous réussis :
```text
Ran 9 tests in 0.161s
OK
```
La suite Python globale a exécuté `28` tests, tous réussis :
```text
Ran 28 tests in 0.468s
OK
```
Les audits documentaires affichent :
```text
Markdown header audit: clean (140 file(s))
Markdown table audit: clean (6 table(s), 140 file(s))
Markdown link audit: clean (137 local link(s), 140 file(s))
```
Le contrôle structurel de la tranche affiche :
```text
0.0.5 alpha.5 Rust façade audit: clean (recursive crate discovery, narrow ownership, regression coverage)
```
`git diff --check` et `git diff --cached --check` n'ont produit aucun diagnostic. Le scope Git observé est exactement celui du delta `alpha.5`.
## Défaut fonctionnel découvert après la gate
La convention recherchée est plus stricte que le contrat implémenté : lorsqu'un item `pub` ou `pub(crate)` est partagé et exposé par la façade crate-root, ses références intra-crate doivent utiliser le chemin canonique `crate::Item`, y compris depuis le module propriétaire.
Le script `version: 1` détecte seulement le sous-cas :
```text
crate::module::Item
```
lorsque `crate::Item` existe déjà. Il laisse notamment passer :
```text
Item
self::Item
super::Item
module::Item
```
ainsi que les noms nus dans des signatures, `impl`, constructeurs ou appels situés dans le module de déclaration. Le compilateur et Clippy ne possèdent pas cette convention de style/architecture : une seule consommation correcte par `crate::Item` peut suffire à rendre le réexport utilisé sans révéler les autres accès non canoniques.
Ce manque est dans le scope exact de `alpha.5`; il doit donc être corrigé par `alpha.5.fix.1`, sans commencer Tauri.
## Résultat
`0.0.5-alpha.5` est enregistré `FAIL` malgré une gate mécanique verte, car l'acceptation fonctionnelle de l'audit est incomplète.
## Transition
Appliquer le présent history puis committer/pousser `0.0.5-alpha.5`.
La correction suivante est :
```text
0.0.5-alpha.5.fix.1
```
Elle doit étendre l'audit de façade pour imposer réellement la forme canonique intra-crate `crate::Item` aux items partagés, ajouter les régressions correspondantes et corriger la documentation/mesure de réconciliation. Aucun scope Tauri ne doit être absorbé.

View File

@@ -0,0 +1,292 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_export_completeness.py
# version: 1
"""Audit crate-root façade completeness and canonical same-crate paths."""
from __future__ import annotations
import argparse
import dataclasses
import pathlib
import re
import sys
from _rust_audit_common import line_depths, mask_source
_IGNORED_DIRECTORY_NAMES = frozenset({
".git",
".venv",
"__pycache__",
"build",
"builds",
"dist",
"node_modules",
"target",
})
@dataclasses.dataclass(frozen=True)
class Candidate:
"""One façade or same-crate path violation candidate."""
code: str
path: str
line: int
message: str
@dataclasses.dataclass(frozen=True)
class Declaration:
"""One module-level public or crate-public declaration."""
module: str
name: str
visibility: str
kind: str
path: pathlib.Path
line: int
def _is_ignored(path: pathlib.Path, root: pathlib.Path) -> bool:
"""Return whether a path belongs to a generated or local-only tree."""
try:
relative = path.relative_to(root)
except ValueError:
return True
return bool(set(relative.parts) & _IGNORED_DIRECTORY_NAMES)
def crate_roots(root: pathlib.Path) -> list[pathlib.Path]:
"""Discover Rust package roots recursively below one project root."""
crates: set[pathlib.Path] = set()
for manifest in root.rglob("Cargo.toml"):
if _is_ignored(manifest, root):
continue
crate = manifest.parent
if (crate / "src/lib.rs").is_file() or (crate / "src/main.rs").is_file():
crates.add(crate)
return sorted(crates)
def crate_root_source(crate: pathlib.Path) -> pathlib.Path | None:
"""Return the library or binary crate-root source when present."""
library = crate / "src/lib.rs"
if library.is_file():
return library
binary = crate / "src/main.rs"
if binary.is_file():
return binary
return None
def module_path(crate: pathlib.Path, path: pathlib.Path) -> str:
"""Return the file-based module path represented by one Rust source file."""
relative = path.relative_to(crate / "src").with_suffix("")
parts = list(relative.parts)
if parts and parts[-1] == "mod":
parts.pop()
return "::".join(parts)
def declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Declaration]:
"""Return root-level public or crate-public declarations from one module file."""
text = path.read_text(encoding="utf-8")
lines = text.splitlines()
masked, _ = mask_source(text)
depths = line_depths(masked)
pattern = re.compile(
r"^\s*(pub(?:\(crate\))?)\s+"
r"(?:(?:async|unsafe|const)\s+)*"
r"(const|static|type|struct|enum|trait|union|fn)\s+"
r"([A-Za-z_][A-Za-z0-9_]*)"
)
found: list[Declaration] = []
for line_number, line in enumerate(lines, 1):
if line_number - 1 >= len(depths) or depths[line_number - 1] != 0:
continue
match = pattern.match(line)
if match is None:
continue
found.append(
Declaration(
module=module_path(crate, path),
name=match.group(3),
visibility=match.group(1),
kind=match.group(2),
path=path,
line=line_number,
)
)
return found
def _parse_export_item(module: str, item: str) -> tuple[tuple[str, str], str] | None:
"""Parse one simple explicit item from a crate-root re-export."""
stripped = item.strip()
if not stripped or stripped == "*" or "{" in stripped or "}" in stripped:
return None
alias_match = re.fullmatch(
r"([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?",
stripped,
)
if alias_match is None:
return None
name = alias_match.group(1)
alias = alias_match.group(2) or name
return (module, name), alias
def root_exports(crate: pathlib.Path) -> dict[tuple[str, str], str]:
"""Return explicit crate-root re-exports keyed by source module and symbol."""
crate_root = crate_root_source(crate)
exports: dict[tuple[str, str], str] = {}
if crate_root is None:
return exports
text = crate_root.read_text(encoding="utf-8")
pattern = re.compile(
r"^\s*pub(?:\(crate\))?\s+use\s+(?:self|crate)::(.*?);",
re.MULTILINE | re.DOTALL,
)
for match in pattern.finditer(text):
body = " ".join(part.strip() for part in match.group(1).splitlines())
if "{" in body and body.endswith("}"):
prefix, group = body.split("{", 1)
module = prefix.rstrip().rstrip(":")
for item in group[:-1].split(","):
parsed = _parse_export_item(module, item)
if parsed is not None:
key, alias = parsed
exports[key] = alias
continue
if "::" not in body:
continue
module, item = body.rsplit("::", 1)
parsed = _parse_export_item(module, item)
if parsed is not None:
key, alias = parsed
exports[key] = alias
return exports
def externally_used(crate: pathlib.Path, declaration: Declaration) -> bool:
"""Return whether a crate-public item is clearly used outside its module file."""
direct = f"crate::{declaration.module}::{declaration.name}"
root_direct = f"crate::{declaration.name}"
for path in sorted(crate.rglob("*.rs")):
if _is_ignored(path, crate) or path.resolve() == declaration.path.resolve():
continue
masked, _ = mask_source(path.read_text(encoding="utf-8"))
if direct in masked or root_direct in masked:
return True
return False
def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]:
"""Audit one Rust package for the selected strict façade contract."""
crate_root = crate_root_source(crate)
if crate_root is None:
return []
exports = root_exports(crate)
candidates: list[Candidate] = []
for path in sorted((crate / "src").rglob("*.rs")):
if path == crate_root or _is_ignored(path, crate):
continue
for declaration in declaration_candidates(crate, path):
required = declaration.visibility == "pub" or externally_used(crate, declaration)
if required and (declaration.module, declaration.name) not in exports:
reason = "public item" if declaration.visibility == "pub" else "crate-public item used outside its module"
candidates.append(
Candidate(
code="RUST-API-201",
path=path.relative_to(workspace).as_posix(),
line=declaration.line,
message=f"{reason} `{declaration.name}` requires an explicit crate-root re-export",
)
)
long_path = re.compile(r"\bcrate::([A-Za-z_][A-Za-z0-9_:]*)::([A-Za-z_][A-Za-z0-9_]*)")
for path in sorted(crate.rglob("*.rs")):
if path == crate_root or _is_ignored(path, crate):
continue
relative = path.relative_to(workspace).as_posix()
masked, _ = mask_source(path.read_text(encoding="utf-8"))
for line_number, line in enumerate(masked.splitlines(), 1):
for match in long_path.finditer(line):
alias = exports.get((match.group(1), match.group(2)))
if alias is None:
continue
candidates.append(
Candidate(
code="RUST-IMPORT-201",
path=relative,
line=line_number,
message=(
f"long same-crate path `crate::{match.group(1)}::{match.group(2)}` "
f"must use crate-root `crate::{alias}`"
),
)
)
return candidates
def audit_workspace(root: pathlib.Path) -> tuple[list[pathlib.Path], list[Candidate]]:
"""Return discovered crates and all façade candidates below one project root."""
crates = crate_roots(root)
candidates = [candidate for crate in crates for candidate in audit_crate(root, crate)]
candidates.sort(key=lambda candidate: (candidate.code, candidate.path, candidate.line, candidate.message))
return crates, candidates
def main() -> int:
"""Run the export-completeness audit."""
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=".")
parser.add_argument("--report-only", action="store_true")
parser.add_argument("--summary-only", action="store_true")
arguments = parser.parse_args()
workspace = pathlib.Path(arguments.root).resolve()
if not workspace.is_dir():
sys.stderr.write(f"Rust export completeness audit: invalid root: {workspace}\n")
return 2
try:
crates, candidates = audit_workspace(workspace)
except (OSError, UnicodeError) as error:
sys.stderr.write(f"Rust export completeness audit: unable to read selected workspace: {error}\n")
return 2
if not crates:
sys.stderr.write("Rust export completeness audit: no Rust crate root discovered\n")
return 2
counts: dict[str, int] = {}
for candidate in candidates:
counts[candidate.code] = counts.get(candidate.code, 0) + 1
sys.stdout.write(f"Rust export completeness audit: {len(candidates)} candidate(s) across {len(crates)} crate(s)\n")
for code in sorted(counts):
sys.stdout.write(f"{code}: {counts[code]}\n")
if not arguments.summary_only:
for candidate in candidates:
sys.stdout.write(f"{candidate.code} {candidate.path}:{candidate.line}: {candidate.message}\n")
return 0 if arguments.report_only or not candidates else 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,132 @@
# file: tests/test_rust_export_completeness.py
# version: 1
"""Regression tests for the optional Rust crate-root façade audit."""
from __future__ import annotations
import pathlib
import subprocess
import sys
import tempfile
import unittest
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "scripts"))
from audit_rust_export_completeness import audit_workspace # noqa: E402
class RustExportCompletenessTests(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = pathlib.Path(self.temporary.name)
def crate(self, relative: str, root_source: str, files: dict[str, str]) -> pathlib.Path:
crate = self.root / relative
(crate / "src").mkdir(parents=True)
(crate / "Cargo.toml").write_text(
'[package]\nname = "fixture"\nversion = "0.1.0"\nedition = "2024"\n',
encoding="utf-8",
)
(crate / "src/lib.rs").write_text(root_source, encoding="utf-8")
for name, text in files.items():
path = crate / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return crate
def candidates(self) -> list[object]:
_, candidates = audit_workspace(self.root)
return candidates
def test_nested_crate_layout_is_discovered(self) -> None:
self.crate("crates/common/example-lib", "mod api;\npub use self::api::Thing;\n", {"src/api.rs": "pub struct Thing;\n"})
crates, candidates = audit_workspace(self.root)
self.assertEqual(len(crates), 1)
self.assertEqual(candidates, [])
def test_missing_public_reexport_is_rejected(self) -> None:
self.crate("crates/example-lib", "mod api;\n", {"src/api.rs": "pub struct Thing;\n"})
candidates = self.candidates()
self.assertEqual([candidate.code for candidate in candidates], ["RUST-API-201"])
def test_external_crate_public_item_requires_reexport(self) -> None:
self.crate(
"crates/example-lib",
"mod api;\nmod consumer;\n",
{
"src/api.rs": "pub(crate) const VALUE: u8 = 1;\n",
"src/consumer.rs": "pub(crate) fn value() -> u8 { return crate::api::VALUE; }\n",
},
)
self.assertTrue(any(candidate.code == "RUST-API-201" for candidate in self.candidates()))
def test_unused_crate_public_item_does_not_force_reexport(self) -> None:
self.crate("crates/example-lib", "mod api;\n", {"src/api.rs": "pub(crate) const VALUE: u8 = 1;\n"})
self.assertEqual(self.candidates(), [])
def test_exported_item_requires_crate_root_path(self) -> None:
self.crate(
"crates/example-lib",
"mod api;\nmod consumer;\npub use self::api::Thing;\n",
{
"src/api.rs": "pub struct Thing;\n",
"src/consumer.rs": "pub(crate) fn make() -> crate::api::Thing { return crate::api::Thing; }\n",
},
)
candidates = self.candidates()
self.assertTrue(any(candidate.code == "RUST-IMPORT-201" for candidate in candidates))
def test_grouped_and_aliased_reexports_are_understood(self) -> None:
self.crate(
"crates/example-lib",
"mod api;\npub use self::api::{First, Second as PublicSecond};\n",
{"src/api.rs": "pub struct First;\npub struct Second;\n"},
)
self.assertEqual(self.candidates(), [])
def test_mod_rs_maps_to_parent_module(self) -> None:
self.crate(
"crates/example-lib",
"mod api;\npub use self::api::Thing;\n",
{"src/api/mod.rs": "pub struct Thing;\n"},
)
self.assertEqual(self.candidates(), [])
def test_comments_and_literals_do_not_create_long_path_candidates(self) -> None:
self.crate(
"crates/example-lib",
"mod api;\nmod consumer;\npub use self::api::Thing;\n",
{
"src/api.rs": "pub struct Thing;\n",
"src/consumer.rs": 'pub(crate) fn text() -> &\'static str { return "crate::api::Thing"; } // crate::api::Thing\n',
},
)
self.assertEqual(self.candidates(), [])
def test_cli_status_for_clean_violation_report_only_and_missing_crates(self) -> None:
script = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "audit_rust_export_completeness.py"
def run(root: pathlib.Path, *extra: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "-B", str(script), "--root", str(root), *extra],
capture_output=True,
text=True,
check=False,
)
empty = self.root / "empty"
empty.mkdir()
self.assertEqual(run(empty).returncode, 2)
self.crate("crates/example-lib", "mod api;\npub use self::api::Thing;\n", {"src/api.rs": "pub struct Thing;\n"})
self.assertEqual(run(self.root).returncode, 0)
(self.root / "crates/example-lib/src/lib.rs").write_text("mod api;\n", encoding="utf-8")
self.assertEqual(run(self.root).returncode, 1)
self.assertEqual(run(self.root, "--report-only").returncode, 0)
if __name__ == "__main__":
unittest.main()