0.0.1-alpha.5.fix.1

This commit is contained in:
2026-09-28 10:02:47 +02:00
parent c17b7068a3
commit a1b56af8ed
23 changed files with 427 additions and 55 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: CHANGELOG.md -->
<!-- version: 7 -->
<!-- version: 8 -->
# Changelog SaseDev Knowledge
@@ -17,6 +17,7 @@ Ce fichier décrit les changements utiles des releases publiées. Les détails d
- instancie le workspace Rust/Cargo strict avec une première crate de domaine et une politique explicite de `Cargo.lock` versionné ;
- introduit les premiers contrats provider-neutral de source, Git et synchronisation sans dépendance à une forge ni à PostgreSQL ;
- active les audits Rust PGR de structure et de façade au moment où des API publiques réelles apparaissent ;
- sélectionne localement `unit_tests/` hors de `src/` pour tous les tests unitaires Rust, avec `tests/` réservé aux tests d'intégration Cargo ;
- fixe le frontend Web PGR comme laboratoire de validation des templates statiques et des assets avant adaptation du markup runtime en Maud ;
- matérialise ce laboratoire sous `Web/knowledge-template-lab/` avec npm, TypeScript strict, Vite, Sass, Bootstrap et manifest Vite ;
- retient l’intégration production Actix/Maud via le manifest Vite comme contrat d’asset mapping à réimplémenter proprement, sans copier mécaniquement le corpus historique ;

14
Cargo.lock generated
View File

@@ -475,46 +475,46 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "knowledge-domain-lib"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
[[package]]
name = "knowledge-git-api"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"knowledge-domain-lib",
]
[[package]]
name = "knowledge-howtos-server"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"knowledge-web-lib",
]
[[package]]
name = "knowledge-pgr-server"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"knowledge-web-lib",
]
[[package]]
name = "knowledge-snippets-server"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"knowledge-web-lib",
]
[[package]]
name = "knowledge-sync-api"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"knowledge-domain-lib",
]
[[package]]
name = "knowledge-web-lib"
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
dependencies = [
"actix-files",
"actix-web",

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 9
# version: 10
[workspace]
resolver = "3"
@@ -14,7 +14,7 @@ members = [
]
[workspace.package]
version = "0.0.1-alpha.5"
version = "0.0.1-alpha.5.fix.1"
edition = "2024"
publish = false
repository = "https://git.sasedev.com/Sasedev/sasedev-knowledge"

View File

@@ -1,5 +1,5 @@
<!-- file: RULES.md -->
<!-- version: 7 -->
<!-- version: 8 -->
# Règles SaseDev Knowledge
@@ -183,6 +183,27 @@ Les trois launchers MUST recevoir leur socket de bind par configuration (`KNOWLE
## Rust et crates
Knowledge sélectionne localement la convention `unit_tests/` séparée que PGR `0.1.1` autorise sans l'imposer globalement.
Pour toutes les crates Rust de ce workspace :
- les tests d'intégration Cargo MUST résider sous `tests/` et consommer l'API publique de la crate ;
- les tests unitaires MUST résider sous `unit_tests/` à la racine de la crate, en miroir logique de `src/` ;
- `src/` MUST NOT contenir de fichier d'implémentation de tests unitaires ni d'attribut `#[test]` ;
- un module source rattache son fichier de tests avec `#[cfg(test)]` et `#[path = "../unit_tests/<fichier>.rs"]` ou le chemin miroir équivalent ;
- les tests unitaires d'un item partagé continuent d'utiliser son identité crate-root conformément à PGR ;
- `tests/` MUST NOT être utilisé pour contourner la visibilité interne ou héberger des tests unitaires qui dépendent d'éléments privés.
Exemple :
```rust
#[cfg(test)]
#[path = "../unit_tests/bite.rs"]
mod tests;
```
Cette convention locale est plus stricte que le `MAY` générique de PGR, mais reste conforme à celui-ci. Si PGR doit l'imposer à tous les futurs workspaces, cette évolution appartient à une version ultérieure du référentiel PGR et non à Knowledge.
Le préfixe de crate retenu est `knowledge-`.
Les responsabilités candidates sont documentées dans l'architecture. Une crate n'est matérialisée que lorsqu'elle possède une responsabilité réelle. Le suffixe `-api` n'est utilisé que pour une frontière passive dont des implémentations distinctes/remplaçables sont réellement prévues.

View File

@@ -1 +1 @@
0.0.1-alpha.5
0.0.1-alpha.5.fix.1

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-domain-lib/src/lib.rs
// version: 2
// version: 3
//! Shared domain contracts for SaseDev Knowledge.
//!
@@ -11,6 +11,3 @@ mod source;
pub use crate::content_family::ContentFamily;
pub use crate::source::{ContentSource, SourceId, SourceIdError};
#[cfg(test)]
mod unit_tests;

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-domain-lib/src/source.rs
// version: 2
// version: 3
//! Source identities independent of repository and forge providers.
@@ -66,3 +66,7 @@ impl crate::ContentSource {
return self.family;
}
}
#[cfg(test)]
#[path = "../unit_tests/source.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-domain-lib/src/unit_tests.rs
// version: 2
// file: crates/knowledge-domain-lib/unit_tests/source.rs
// version: 1
#[test]
fn source_id_rejects_empty_values() {

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-git-api/src/lib.rs
// version: 1
// version: 2
//! Provider-neutral contracts for Git-backed Knowledge sources.
//!
@@ -54,4 +54,5 @@ pub trait GitRepository {
}
#[cfg(test)]
mod unit_tests;
#[path = "../unit_tests/lib.rs"]
mod tests;

View File

@@ -1,4 +1,4 @@
// file: crates/knowledge-git-api/src/unit_tests.rs
// file: crates/knowledge-git-api/unit_tests/lib.rs
// version: 1
#[derive(Debug, Eq, PartialEq)]

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-sync-api/src/lib.rs
// version: 1
// version: 2
//! Contracts for provider-neutral repository synchronization signals.
//!
@@ -54,4 +54,5 @@ pub trait RepositoryEventSource {
}
#[cfg(test)]
mod unit_tests;
#[path = "../unit_tests/lib.rs"]
mod tests;

View File

@@ -1,4 +1,4 @@
// file: crates/knowledge-sync-api/src/unit_tests.rs
// file: crates/knowledge-sync-api/unit_tests/lib.rs
// version: 1
#[derive(Debug, Eq, PartialEq)]

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-web-lib/src/assets.rs
// version: 1
// version: 2
//! Vite development and production asset resolution.
@@ -193,3 +193,7 @@ fn local_static_mount_path(base: &str) -> Option<String> {
}
return Some(format!("{}/assets", normalized.trim_end_matches('/')));
}
#[cfg(test)]
#[path = "../unit_tests/assets.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-web-lib/src/lib.rs
// version: 1
// version: 2
//! Shared Actix Web and Maud composition for the three Knowledge server launchers.
//!
@@ -22,6 +22,3 @@ pub(crate) use self::assets::ViteAssets;
pub(crate) use self::assets::load_assets_from_environment;
/// Internal Maud renderer for the public foundation page.
pub(crate) use self::page::render_index_page;
#[cfg(test)]
mod unit_tests;

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-web-lib/src/page.rs
// version: 1
// version: 2
//! Maud rendering adapted from the static template-lab contract.
@@ -50,3 +50,7 @@ fn family_slug(family: knowledge_domain_lib::ContentFamily) -> &'static str {
knowledge_domain_lib::ContentFamily::ProjectLocal => return "project-local",
}
}
#[cfg(test)]
#[path = "../unit_tests/page.rs"]
mod tests;

View File

@@ -1,7 +1,7 @@
// file: crates/knowledge-web-lib/src/unit_tests.rs
// file: crates/knowledge-web-lib/unit_tests/assets.rs
// version: 1
//! Unit tests for shared Web composition primitives.
//! Unit tests for Vite asset resolution.
#[test]
fn development_assets_include_vite_client_before_main_entry() {
@@ -50,23 +50,3 @@ fn manifest_requires_the_stable_main_entry() {
let result = crate::ViteAssets::from_manifest_text(manifest, "/", std::path::PathBuf::from("/tmp/knowledge-assets"));
assert!(result.is_err());
}
#[test]
fn maud_page_uses_resolved_assets_and_site_identity() {
let assets = match crate::ViteAssets::from_manifest_text(
r#"{"src/main.ts": {"file": "assets/main.js", "css": ["assets/main.css"], "isEntry": true}}"#,
"/",
std::path::PathBuf::from("/tmp/knowledge-assets"),
) {
Ok(value) => value,
Err(error) => {
assert!(error.is_empty(), "unexpected manifest error: {error}");
return;
},
};
let rendered = crate::render_index_page(crate::ServerSite::Pgr, &assets).into_string();
assert!(rendered.contains("SaseDev Knowledge — PGR"));
assert!(rendered.contains("/assets/main.js"));
assert!(rendered.contains("/assets/main.css"));
assert!(rendered.contains("data-content-family=\"pgr\""));
}

View File

@@ -0,0 +1,24 @@
// file: crates/knowledge-web-lib/unit_tests/page.rs
// version: 1
//! Unit tests for Maud page rendering.
#[test]
fn maud_page_uses_resolved_assets_and_site_identity() {
let assets = match crate::ViteAssets::from_manifest_text(
r#"{"src/main.ts": {"file": "assets/main.js", "css": ["assets/main.css"], "isEntry": true}}"#,
"/",
std::path::PathBuf::from("/tmp/knowledge-assets"),
) {
Ok(value) => value,
Err(error) => {
assert!(error.is_empty(), "unexpected manifest error: {error}");
return;
},
};
let rendered = crate::render_index_page(crate::ServerSite::Pgr, &assets).into_string();
assert!(rendered.contains("SaseDev Knowledge — PGR"));
assert!(rendered.contains("/assets/main.js"));
assert!(rendered.contains("/assets/main.css"));
assert!(rendered.contains("data-content-family=\"pgr\""));
}

View File

@@ -0,0 +1,130 @@
<!-- file: deltas/0.0.1/alpha.5.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.1-alpha.5.fix.1 — layout séparé des tests unitaires Rust
## Base requise
État `0.0.1-alpha.5` committé et poussé après application du history :
```text
history/0.0.1/alpha.5.md = FAIL
```
La version devient :
```text
0.0.1-alpha.5.fix.1
```
## Cause
La gate technique d'`alpha.5` passe, y compris les tests de `knowledge-web-lib` et les smokes HTTP.
La revue de gouvernance opérateur relève cependant que les tests unitaires restent sous `src/unit_tests.rs`.
PGR `0.1.1` autorise une arborescence `unit_tests/` séparée avec la modalité `MAY`. Knowledge sélectionne désormais explicitement cette convention comme règle locale `MUST`.
Le défaut concerne quatre crates et doit être corrigé de manière cohérente.
## Arborescence cible
```text
crates/<crate>/
src/
unit_tests/
tests/
```
- `unit_tests/` : tests unitaires attachés aux modules source par `#[path = "..."]` ;
- `tests/` : tests d'intégration Cargo qui consomment l'API publique.
Exemple :
```rust
#[cfg(test)]
#[path = "../unit_tests/source.rs"]
mod tests;
```
## ADD
```text
crates/knowledge-domain-lib/unit_tests/source.rs
crates/knowledge-git-api/unit_tests/lib.rs
crates/knowledge-sync-api/unit_tests/lib.rs
crates/knowledge-web-lib/unit_tests/assets.rs
crates/knowledge-web-lib/unit_tests/page.rs
scripts/audit_rust_test_layout.py
manifests/0.0.1/alpha.5.fix.1.delete.txt
deltas/0.0.1/alpha.5.fix.1.md
```
## REPLACE
```text
VERSION
Cargo.toml
CHANGELOG.md
RULES.md
crates/knowledge-domain-lib/src/lib.rs
crates/knowledge-domain-lib/src/source.rs
crates/knowledge-git-api/src/lib.rs
crates/knowledge-sync-api/src/lib.rs
crates/knowledge-web-lib/src/lib.rs
crates/knowledge-web-lib/src/assets.rs
crates/knowledge-web-lib/src/page.rs
docs/plans/001-V0_0_1_FOUNDATION_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
## DELETE
```text
crates/knowledge-domain-lib/src/unit_tests.rs
crates/knowledge-git-api/src/unit_tests.rs
crates/knowledge-sync-api/src/unit_tests.rs
crates/knowledge-web-lib/src/unit_tests.rs
```
Les suppressions sont possédées par :
```text
manifests/0.0.1/alpha.5.fix.1.delete.txt
```
## Audit ajouté
`audit_rust_test_layout.py` vérifie :
- absence de `#[test]` sous `src/` ;
- absence de fichiers `src/tests.rs` et `src/unit_tests.rs` ;
- absence de `mod unit_tests;` classique ;
- rattachement effectif de chaque fichier sous `unit_tests/` par un `#[path = "..."]`.
L'audit ne traite pas `tests/` comme tests internes.
## Scope fonctionnel
Aucun comportement métier ou HTTP n'est modifié.
Les routes, assets, rendu Maud, manifest Vite, binds et trois launchers restent identiques à `alpha.5`.
## Application des suppressions
Après extraction du delta, appliquer exactement le manifest :
```bash
while IFS= read -r path; do
test -n "$path" || continue
rm -f -- "$path"
done < manifests/0.0.1/alpha.5.fix.1.delete.txt
```
Ne pas supprimer d'autre chemin.
## Validation attendue
La gate doit prouver l'arborescence, les rattachements, les audits Rust, Cargo/Clippy/tests, la baseline Web et les trois smokes HTTP.
Ne pas committe `alpha.5.fix.1` avant production de son history à partir des résultats opérateur.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/001-V0_0_1_FOUNDATION_PLAN.md -->
<!-- version: 11 -->
<!-- version: 12 -->
# Plan 0.0.1 — fondation SaseDev Knowledge
@@ -203,6 +203,18 @@ La gate doit couvrir les tests `knowledge-web-lib`, la compilation/Clippy des qu
La tranche ne doit pas introduire admin complète, auth complète, persistance métier complète, logging transverse artificiel ni forge obligatoire.
La gate technique d'`alpha.5` est verte : structure, Web, Cargo, Clippy, tests et smokes HTTP passent. La tranche reste néanmoins **FAIL** après revue opérateur de gouvernance : les tests unitaires sont encore implémentés sous `src/unit_tests.rs`, alors que Knowledge sélectionne désormais la convention PGR optionnelle `unit_tests/` séparée et miroir de `src/`.
Le défaut existait déjà dans les trois crates antérieures et est également introduit par `knowledge-web-lib`; il doit donc être corrigé globalement et non seulement dans la nouvelle crate.
`alpha.5.fix.1` :
- formalise cette convention locale comme `MUST` dans `RULES.md` ;
- déplace les tests unitaires des quatre crates concernées sous `unit_tests/` ;
- rattache les tests avec `#[cfg(test)]` + `#[path = "..."]` ;
- conserve `tests/` pour les tests d'intégration Cargo uniquement ;
- ajoute un audit monotâche empêchant le retour de `#[test]` ou de fichiers `unit_tests.rs` sous `src/`.
### beta.1 — réconciliation de fondation
Rejouer les gates pertinentes, réconcilier docs/architecture avec ce qui existe réellement, traiter les décisions devenues nécessaires et effectuer la revue d'horizon.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
<!-- version: 11 -->
<!-- version: 12 -->
# Matrice de validation Knowledge
@@ -316,3 +316,50 @@ La composition serveur doit prouver :
- audits Rust PGR, `cargo check`, Clippy strict et tests ciblés `PASS` ;
- smoke HTTP production `PASS` pour les trois launchers avec ports de test injectés par environnement ;
- `Cargo.lock` mis à jour/versionné et staging propre.
## Convention locale des tests Rust
Knowledge sélectionne la convention PGR optionnelle suivante comme règle locale obligatoire :
```text
crate/
src/
unit_tests/
tests/
```
`unit_tests/` contient les tests unitaires attachés aux modules source par `#[path = "..."]`.
`tests/` contient exclusivement les tests d'intégration Cargo qui consomment l'API publique.
La gate Rust ajoute :
```text
python3 -B scripts/audit_rust_test_layout.py crates
```
Cet audit doit refuser notamment :
- un attribut `#[test]` dans `src/` ;
- un fichier `src/unit_tests.rs` ou `src/tests.rs` ;
- un `mod unit_tests;` classique sous `src/` ;
- un fichier sous `unit_tests/` qui n'est référencé par aucun `#[path = "..."]` d'un source de la crate.
## Gate de `0.0.1-alpha.5.fix.1`
Le fix de layout des tests doit prouver :
- `VERSION = 0.0.1-alpha.5.fix.1` et version workspace identique ;
- history `alpha.5` `FAIL` committé avant application ;
- suppression exacte des quatre anciens `src/unit_tests.rs` via le manifest de suppression ;
- tests unitaires sous `unit_tests/` hors de `src/` pour `knowledge-domain-lib`, `knowledge-git-api`, `knowledge-sync-api` et `knowledge-web-lib` ;
- rattachements `#[cfg(test)]` + `#[path = "..."]` depuis les modules source propriétaires ;
- aucun `#[test]` sous `crates/*/src/` ;
- `tests/` réservé aux futurs tests d'intégration Cargo ;
- `audit_rust_test_layout.py` `PASS` ;
- audits Rust PGR existants toujours `PASS`, y compris export completeness qui suit les modules `#[path]` ;
- `cargo check`, Clippy strict et `cargo test --workspace --all-targets --all-features` `PASS` ;
- baseline Web `npm ci`, typecheck et build `PASS` ;
- smoke HTTP production des trois serveurs `PASS` ;
- aucune modification fonctionnelle Actix/Maud/Vite au-delà du rattachement des tests ;
- `git diff --cached --check` propre.

View File

@@ -0,0 +1,63 @@
<!-- file: history/0.0.1/alpha.5.fix.1.md -->
<!-- version: 1 -->
# History 0.0.1-alpha.5.fix.1 — layout séparé des tests unitaires Rust
## Statut
FAIL
La gate opérateur de `0.0.1-alpha.5.fix.1` exécutée le `2026-09-28` confirme la migration fonctionnelle des tests unitaires hors de `src/`, mais le premier contrôle d'hygiène Git échoue sur deux lignes vides terminales.
## Baseline opérateur
`alpha.5` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.1/alpha.5.md = FAIL
commit c17b706
message 0.0.1-alpha.5
branch master
push origin/master PASS
status clean après push
```
Le push observé a avancé `origin/master` de `8f6f072` à `c17b706`.
## Layout des tests
Le manifest de suppression contient exactement les quatre anciens `src/unit_tests.rs`, qui sont supprimés avant la gate. La preuve structurelle passe :
```text
Knowledge 0.0.1-alpha.5.fix.1 test layout: clean
Rust test-layout audit: clean (7 crate(s))
```
Les cinq fichiers unitaires résident désormais sous `unit_tests/` et leurs tests apparaissent sous les modules propriétaires (`source::tests`, `assets::tests`, `page::tests`).
## Défaut bloquant initial
Le premier `git diff --check` échoue avec :
```text
crates/knowledge-domain-lib/src/lib.rs:14: new blank line at EOF.
crates/knowledge-web-lib/src/lib.rs:25: new blank line at EOF.
```
La tranche reste donc `FAIL`.
## Normalisation et preuves suivantes
`cargo fmt --all` puis `cargo fmt --all -- --check` passent et normalisent les fins de fichiers. Les audits Rust passent sur 18 fichiers et 7 crates/packages. `cargo check`, Clippy strict et tous les tests workspace passent : 3 + 1 + 1 + 4 tests, launchers à 0 test.
La baseline Web passe également : `npm ci`, typecheck et build Vite. Les warnings Sass Bootstrap et `@parcel/watcher@2.6.0` restent des observations non bloquantes déjà connues.
Le transcript du fix ne contient pas le rejeu demandé des trois smokes HTTP. La preuve historique d'`alpha.5` ne remplace pas cette preuve de fix.
Le `git diff --cached --check` final est propre et le staging reflète les renommages/suppressions gouvernés ainsi que les cinq fichiers `unit_tests/`.
## Décision
`0.0.1-alpha.5.fix.1` est `FAIL`.
La transition autorisée est `0.0.1-alpha.5.fix.2`, depuis le snapshot FAIL déjà normalisé, avec gate sans formatage mutateur préalable et trois smokes HTTP explicitement rejoués.

View File

@@ -0,0 +1,4 @@
crates/knowledge-domain-lib/src/unit_tests.rs
crates/knowledge-git-api/src/unit_tests.rs
crates/knowledge-sync-api/src/unit_tests.rs
crates/knowledge-web-lib/src/unit_tests.rs

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_test_layout.py
# version: 1
"""Audit the Knowledge Rust unit/integration test layout."""
from __future__ import annotations
import argparse
import pathlib
import re
import sys
_TEST_ATTRIBUTE = re.compile(r"(?m)^\s*#\s*\[\s*test\s*\]")
_PATH_ATTRIBUTE = re.compile(r'#\s*\[\s*path\s*=\s*"([^"]+)"\s*\]')
_FORBIDDEN_MODULE = re.compile(r"(?m)^\s*mod\s+unit_tests\s*;")
def audit_crate(crate_root: pathlib.Path) -> list[str]:
"""Return layout violations for one crate."""
errors: list[str] = []
src_root = crate_root / "src"
referenced_unit_tests: set[pathlib.Path] = set()
if src_root.is_dir():
for source in sorted(src_root.rglob("*.rs")):
text = source.read_text(encoding="utf-8")
relative = source.relative_to(crate_root)
if _TEST_ATTRIBUTE.search(text):
errors.append(f"{crate_root.name}/{relative}: #[test] must live under unit_tests/")
if source.name in {"tests.rs", "unit_tests.rs"}:
errors.append(f"{crate_root.name}/{relative}: test implementation file must not live under src/")
if _FORBIDDEN_MODULE.search(text):
errors.append(f"{crate_root.name}/{relative}: use #[path = \"../unit_tests/...\"] instead of mod unit_tests;")
for match in _PATH_ATTRIBUTE.finditer(text):
candidate = (source.parent / match.group(1)).resolve()
try:
candidate.relative_to((crate_root / "unit_tests").resolve())
except ValueError:
continue
referenced_unit_tests.add(candidate)
unit_root = crate_root / "unit_tests"
if unit_root.is_dir():
for test_file in sorted(unit_root.rglob("*.rs")):
resolved = test_file.resolve()
if resolved not in referenced_unit_tests:
relative = test_file.relative_to(crate_root)
errors.append(f"{crate_root.name}/{relative}: unit test file is not attached by any #[path] module")
return errors
def main() -> int:
"""Run the audit."""
parser = argparse.ArgumentParser()
parser.add_argument("root", nargs="?", default="crates")
arguments = parser.parse_args()
root = pathlib.Path(arguments.root)
errors: list[str] = []
crate_count = 0
for manifest in sorted(root.glob("*/Cargo.toml")):
crate_count += 1
errors.extend(audit_crate(manifest.parent))
if errors:
for error in errors:
print(error, file=sys.stderr)
return 1
print(f"Rust test-layout audit: clean ({crate_count} crate(s))")
return 0
if __name__ == "__main__":
raise SystemExit(main())