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 --> <!-- file: CHANGELOG.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Changelog SaseDev Knowledge # 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é ; - 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 ; - 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 ; - 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 ; - 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 ; - 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 ; - 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]] [[package]]
name = "knowledge-domain-lib" name = "knowledge-domain-lib"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
[[package]] [[package]]
name = "knowledge-git-api" name = "knowledge-git-api"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"knowledge-domain-lib", "knowledge-domain-lib",
] ]
[[package]] [[package]]
name = "knowledge-howtos-server" name = "knowledge-howtos-server"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"knowledge-web-lib", "knowledge-web-lib",
] ]
[[package]] [[package]]
name = "knowledge-pgr-server" name = "knowledge-pgr-server"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"knowledge-web-lib", "knowledge-web-lib",
] ]
[[package]] [[package]]
name = "knowledge-snippets-server" name = "knowledge-snippets-server"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"knowledge-web-lib", "knowledge-web-lib",
] ]
[[package]] [[package]]
name = "knowledge-sync-api" name = "knowledge-sync-api"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"knowledge-domain-lib", "knowledge-domain-lib",
] ]
[[package]] [[package]]
name = "knowledge-web-lib" name = "knowledge-web-lib"
version = "0.0.1-alpha.5" version = "0.0.1-alpha.5.fix.1"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: RULES.md --> <!-- file: RULES.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Règles SaseDev Knowledge # Règles SaseDev Knowledge
@@ -183,6 +183,27 @@ Les trois launchers MUST recevoir leur socket de bind par configuration (`KNOWLE
## Rust et crates ## 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-`. 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. 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 // file: crates/knowledge-domain-lib/src/lib.rs
// version: 2 // version: 3
//! Shared domain contracts for SaseDev Knowledge. //! Shared domain contracts for SaseDev Knowledge.
//! //!
@@ -11,6 +11,3 @@ mod source;
pub use crate::content_family::ContentFamily; pub use crate::content_family::ContentFamily;
pub use crate::source::{ContentSource, SourceId, SourceIdError}; 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 // file: crates/knowledge-domain-lib/src/source.rs
// version: 2 // version: 3
//! Source identities independent of repository and forge providers. //! Source identities independent of repository and forge providers.
@@ -66,3 +66,7 @@ impl crate::ContentSource {
return self.family; 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 // file: crates/knowledge-domain-lib/unit_tests/source.rs
// version: 2 // version: 1
#[test] #[test]
fn source_id_rejects_empty_values() { fn source_id_rejects_empty_values() {

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-git-api/src/lib.rs // file: crates/knowledge-git-api/src/lib.rs
// version: 1 // version: 2
//! Provider-neutral contracts for Git-backed Knowledge sources. //! Provider-neutral contracts for Git-backed Knowledge sources.
//! //!
@@ -54,4 +54,5 @@ pub trait GitRepository {
} }
#[cfg(test)] #[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 // version: 1
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-sync-api/src/lib.rs // file: crates/knowledge-sync-api/src/lib.rs
// version: 1 // version: 2
//! Contracts for provider-neutral repository synchronization signals. //! Contracts for provider-neutral repository synchronization signals.
//! //!
@@ -54,4 +54,5 @@ pub trait RepositoryEventSource {
} }
#[cfg(test)] #[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 // version: 1
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-web-lib/src/assets.rs // file: crates/knowledge-web-lib/src/assets.rs
// version: 1 // version: 2
//! Vite development and production asset resolution. //! 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('/'))); 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 // file: crates/knowledge-web-lib/src/lib.rs
// version: 1 // version: 2
//! Shared Actix Web and Maud composition for the three Knowledge server launchers. //! 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; pub(crate) use self::assets::load_assets_from_environment;
/// Internal Maud renderer for the public foundation page. /// Internal Maud renderer for the public foundation page.
pub(crate) use self::page::render_index_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 // file: crates/knowledge-web-lib/src/page.rs
// version: 1 // version: 2
//! Maud rendering adapted from the static template-lab contract. //! 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", 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 // version: 1
//! Unit tests for shared Web composition primitives. //! Unit tests for Vite asset resolution.
#[test] #[test]
fn development_assets_include_vite_client_before_main_entry() { 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")); let result = crate::ViteAssets::from_manifest_text(manifest, "/", std::path::PathBuf::from("/tmp/knowledge-assets"));
assert!(result.is_err()); 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 --> <!-- file: docs/plans/001-V0_0_1_FOUNDATION_PLAN.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Plan 0.0.1 — fondation SaseDev Knowledge # 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 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 ### 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. 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 --> <!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Matrice de validation Knowledge # 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` ; - 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 ; - smoke HTTP production `PASS` pour les trois launchers avec ports de test injectés par environnement ;
- `Cargo.lock` mis à jour/versionné et staging propre. - `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())