0.0.1-alpha.5

This commit is contained in:
2026-09-28 09:50:20 +02:00
parent 8f6f072e11
commit c17b7068a3
26 changed files with 1952 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: CHANGELOG.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Changelog SaseDev Knowledge
@@ -20,5 +20,6 @@ Ce fichier décrit les changements utiles des releases publiées. Les détails d
- 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 ;
- matérialise `knowledge-web-lib` et trois launchers Actix distincts avec rendu Maud, dev-server Vite configurable, résolution du manifest de production et service des assets hashés ;
- conserve comme corpus d'inspiration séparé les archives historiques `actix-auth-app` et `amahil-project`, sans importer leurs fonctionnalités inachevées comme exigences ;
- prépare un workspace Rust et une baseline Web composables sans construire prématurément la plateforme complète.

1160
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 8
# version: 9
[workspace]
resolver = "3"
@@ -7,16 +7,21 @@ members = [
"crates/knowledge-domain-lib",
"crates/knowledge-git-api",
"crates/knowledge-sync-api",
"crates/knowledge-web-lib",
"crates/knowledge-pgr-server",
"crates/knowledge-howtos-server",
"crates/knowledge-snippets-server",
]
[workspace.package]
version = "0.0.1-alpha.4.fix.2"
version = "0.0.1-alpha.5"
edition = "2024"
publish = false
repository = "https://git.sasedev.com/Sasedev/sasedev-knowledge"
[workspace.dependencies]
knowledge-domain-lib = { path = "crates/knowledge-domain-lib" }
knowledge-web-lib = { path = "crates/knowledge-web-lib" }
[workspace.lints.rust]
missing_docs = "warn"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# SaseDev Knowledge
@@ -31,7 +31,7 @@ PostgreSQL est mutualisable et constitue la baseline retenue pour l'état transv
Chaque application Web doit pouvoir exposer sa propre surface d'administration protégée pour son domaine. Les chemins HTTP exacts ne sont pas encore figés. Une éventuelle quatrième application d'administration pourra agréger des fonctions transversales, mais elle ne remplacera pas les contrôles d'autorisation propres aux trois serveurs.
Le workspace Rust matérialise désormais trois frontières passives avant toute implémentation technique : `knowledge-domain-lib` pour les identités de famille/source, `knowledge-git-api` pour le contrat Git générique et `knowledge-sync-api` pour les signaux de synchronisation. Ces deux crates `-api` sont justifiées par des implémentations remplaçables ou multiples ; elles ne dépendent ni d'Actix, ni de Gitea, ni de PostgreSQL.
Le workspace Rust matérialise les trois frontières passives de fondation : `knowledge-domain-lib` pour les identités de famille/source, `knowledge-git-api` pour le contrat Git générique et `knowledge-sync-api` pour les signaux de synchronisation. Ces deux crates `-api` sont justifiées par des implémentations remplaçables ou multiples ; elles ne dépendent ni d'Actix, ni de Gitea, ni de PostgreSQL.
## Frontend de référence et rendu Maud
@@ -43,6 +43,10 @@ Les templates statiques sont un **harness de conception/validation**, pas le mot
Le mécanisme observé dans `actix-auth-app` est documenté comme corpus technique à adapter, pas comme code autoritaire à copier.
`alpha.5` matérialise `knowledge-web-lib` et les trois launchers `knowledge-*-server`. La bibliothèque commune possède le rendu Maud, la sélection dev-server/manifest Vite et le service des assets de production ; les trois `main.rs` ne contiennent que la sélection de façade et du nom de variable d'environnement de bind.
Le runtime exige un bind configuré par launcher et l'une des deux sources d'assets suivantes : `KNOWLEDGE_VITE_DEV_ORIGIN` en développement ou `KNOWLEDGE_VITE_MANIFEST_PATH` en production. `KNOWLEDGE_ASSET_BASE_PATH` peut remplacer le préfixe `/` de production. Aucune route admin/login n'est matérialisée à ce stade.
## Invariants de fondation
- Git est la source autoritaire du contenu versionné.

View File

@@ -1,5 +1,5 @@
<!-- file: RULES.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Règles SaseDev Knowledge
@@ -177,6 +177,10 @@ Le build Vite de production MUST produire un mapping machine-readable des entré
Le pattern de loader observé dans `actix-auth-app` est un corpus d’inspiration. Sa reprise doit être réévaluée selon les règles Rust Knowledge : pas de `panic!`/`unwrap`/`expect`, erreurs récupérables, API minimale, tests ciblés et séparation claire entre debug/dev et production.
`knowledge-web-lib` possède ce contrat partagé à partir d'`alpha.5`. En développement, `KNOWLEDGE_VITE_DEV_ORIGIN` MUST fournir l'origin du dev server et le renderer charge `@vite/client` puis `src/main.ts`. En production, `KNOWLEDGE_VITE_MANIFEST_PATH` MUST désigner le manifest généré et les noms hashés sont résolus depuis l'entrée stable `src/main.ts`. `KNOWLEDGE_ASSET_BASE_PATH` MAY remplacer le préfixe de production `/`.
Les trois launchers MUST recevoir leur socket de bind par configuration (`KNOWLEDGE_PGR_BIND_ADDRESS`, `KNOWLEDGE_HOWTOS_BIND_ADDRESS`, `KNOWLEDGE_SNIPPETS_BIND_ADDRESS`). Aucun port de déploiement n'est choisi par le code. Les seules routes matérialisées par la fondation sont `/` et `/healthz`; cela ne fixe aucune convention future pour l'authentification ou l'administration.
## Rust et crates
Le préfixe de crate retenu est `knowledge-`.

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: Web/knowledge-template-lab/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Knowledge template lab
@@ -43,7 +43,7 @@ L'entrée stable de départ est :
src/main.ts
```
Cette entrée importe également la chaîne Sass. L'intégration Maud prévue en `alpha.5` devra résoudre les fichiers hashés depuis le manifest plutôt que coder leurs noms.
Cette entrée importe également la chaîne Sass. À partir d'`alpha.5`, `knowledge-web-lib` résout effectivement les fichiers hashés depuis ce manifest plutôt que coder leurs noms. En développement, la même bibliothèque peut cibler un origin Vite configuré et charger `@vite/client` puis `src/main.ts`.
## Routes et administration

View File

@@ -0,0 +1,15 @@
# file: crates/knowledge-howtos-server/Cargo.toml
# version: 1
[package]
name = "knowledge-howtos-server"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-web-lib.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,8 @@
// file: crates/knowledge-howtos-server/src/main.rs
// version: 1
//! Thin launcher for the knowledge-howtos-server process.
fn main() -> std::io::Result<()> {
return knowledge_web_lib::run_server(knowledge_web_lib::ServerSite::Howtos, "KNOWLEDGE_HOWTOS_BIND_ADDRESS");
}

View File

@@ -0,0 +1,15 @@
# file: crates/knowledge-pgr-server/Cargo.toml
# version: 1
[package]
name = "knowledge-pgr-server"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-web-lib.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,8 @@
// file: crates/knowledge-pgr-server/src/main.rs
// version: 1
//! Thin launcher for the knowledge-pgr-server process.
fn main() -> std::io::Result<()> {
return knowledge_web_lib::run_server(knowledge_web_lib::ServerSite::Pgr, "KNOWLEDGE_PGR_BIND_ADDRESS");
}

View File

@@ -0,0 +1,15 @@
# file: crates/knowledge-snippets-server/Cargo.toml
# version: 1
[package]
name = "knowledge-snippets-server"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-web-lib.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,8 @@
// file: crates/knowledge-snippets-server/src/main.rs
// version: 1
//! Thin launcher for the knowledge-snippets-server process.
fn main() -> std::io::Result<()> {
return knowledge_web_lib::run_server(knowledge_web_lib::ServerSite::Snippets, "KNOWLEDGE_SNIPPETS_BIND_ADDRESS");
}

View File

@@ -0,0 +1,20 @@
# file: crates/knowledge-web-lib/Cargo.toml
# version: 1
[package]
name = "knowledge-web-lib"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
actix-files = { version = "0.7.0", default-features = false }
actix-web = { version = "4.15.0", default-features = false }
knowledge-domain-lib.workspace = true
maud = { version = "0.27.0", features = ["actix-web"] }
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
[lints]
workspace = true

View File

@@ -0,0 +1,195 @@
// file: crates/knowledge-web-lib/src/assets.rs
// version: 1
//! Vite development and production asset resolution.
#[derive(Clone, Debug, serde::Deserialize)]
struct ManifestEntry {
file: String,
#[serde(default)]
css: Vec<String>,
#[serde(default, rename = "isEntry")]
is_entry: bool,
}
/// Internal resolved asset set consumed by the Maud renderer and Actix runtime.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ViteAssets {
scripts: Vec<String>,
stylesheets: Vec<String>,
static_assets_directory: Option<std::path::PathBuf>,
static_mount_path: Option<String>,
}
impl crate::ViteAssets {
/// Build development asset references from a configured Vite origin.
pub(crate) fn development(origin: &str) -> Result<crate::ViteAssets, String> {
let normalized = origin.trim().trim_end_matches('/');
if normalized.is_empty() {
return Err("KNOWLEDGE_VITE_DEV_ORIGIN must not be empty".to_owned());
}
return Ok(crate::ViteAssets {
scripts: vec![format!("{normalized}/@vite/client"), format!("{normalized}/src/main.ts")],
stylesheets: Vec::new(),
static_assets_directory: None,
static_mount_path: None,
});
}
/// Resolve production asset references from Vite manifest JSON text.
pub(crate) fn from_manifest_text(manifest: &str, asset_base: &str, static_assets_directory: std::path::PathBuf) -> Result<crate::ViteAssets, String> {
let entries: std::collections::BTreeMap<String, ManifestEntry> = match serde_json::from_str(manifest) {
Ok(value) => value,
Err(error) => return Err(format!("invalid Vite manifest JSON: {error}")),
};
let entry = match entries.get("src/main.ts") {
Some(value) => value,
None => return Err("Vite manifest is missing src/main.ts".to_owned()),
};
if !entry.is_entry {
return Err("Vite manifest src/main.ts is not marked as an entry".to_owned());
}
if entry.file.trim().is_empty() {
return Err("Vite manifest src/main.ts has an empty JavaScript file".to_owned());
}
let javascript = match asset_url(asset_base, entry.file.as_str()) {
Ok(value) => value,
Err(error) => return Err(error),
};
let mut stylesheets = Vec::new();
for stylesheet in &entry.css {
let resolved = match asset_url(asset_base, stylesheet.as_str()) {
Ok(value) => value,
Err(error) => return Err(error),
};
stylesheets.push(resolved);
}
let static_mount_path = local_static_mount_path(asset_base);
return Ok(crate::ViteAssets {
scripts: vec![javascript],
stylesheets,
static_assets_directory: Some(static_assets_directory),
static_mount_path,
});
}
/// Return module script URLs in load order.
pub(crate) fn scripts(&self) -> &[String] {
return self.scripts.as_slice();
}
/// Return production stylesheet URLs. Development CSS is injected by Vite.
pub(crate) fn stylesheets(&self) -> &[String] {
return self.stylesheets.as_slice();
}
/// Return the local production assets directory when static serving is available.
pub(crate) fn static_assets_directory(&self) -> Option<&std::path::Path> {
match self.static_assets_directory.as_deref() {
Some(value) => return Some(value),
None => return None,
}
}
/// Return the local HTTP mount used for built assets, or `None` for an external asset base.
pub(crate) fn static_mount_path(&self) -> Option<&str> {
match self.static_mount_path.as_deref() {
Some(value) => return Some(value),
None => return None,
}
}
}
/// Resolve the active Vite asset mode from process environment variables.
pub(crate) fn load_assets_from_environment() -> Result<crate::ViteAssets, std::io::Error> {
let development_origin = environment_value("KNOWLEDGE_VITE_DEV_ORIGIN");
let manifest_path = environment_value("KNOWLEDGE_VITE_MANIFEST_PATH");
match (development_origin, manifest_path) {
(Some(_), Some(_)) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"configure either KNOWLEDGE_VITE_DEV_ORIGIN or KNOWLEDGE_VITE_MANIFEST_PATH, not both",
));
},
(Some(origin), None) => {
let assets = match crate::ViteAssets::development(origin.as_str()) {
Ok(value) => value,
Err(error) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, error)),
};
return Ok(assets);
},
(None, Some(path)) => {
return load_manifest_assets(path.as_str());
},
(None, None) => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "configure KNOWLEDGE_VITE_DEV_ORIGIN or KNOWLEDGE_VITE_MANIFEST_PATH"));
},
}
}
fn environment_value(name: &str) -> Option<String> {
match std::env::var(name) {
Ok(value) => {
if value.trim().is_empty() {
return None;
}
return Some(value);
},
Err(_) => return None,
}
}
fn load_manifest_assets(path: &str) -> Result<crate::ViteAssets, std::io::Error> {
let manifest_path = std::path::PathBuf::from(path);
let text = match std::fs::read_to_string(manifest_path.as_path()) {
Ok(value) => value,
Err(error) => return Err(error),
};
let manifest_directory = match manifest_path.parent() {
Some(value) => value,
None => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Vite manifest path has no parent directory"));
},
};
let output_root = match manifest_directory.parent() {
Some(value) => value,
None => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Vite manifest path must be located below an output root"));
},
};
let asset_base = match environment_value("KNOWLEDGE_ASSET_BASE_PATH") {
Some(value) => value,
None => "/".to_owned(),
};
let assets = match crate::ViteAssets::from_manifest_text(text.as_str(), asset_base.as_str(), output_root.join("assets")) {
Ok(value) => value,
Err(error) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error)),
};
return Ok(assets);
}
fn asset_url(base: &str, relative: &str) -> Result<String, String> {
let normalized_base = base.trim();
let normalized_relative = relative.trim().trim_start_matches('/');
if normalized_base.is_empty() {
return Err("KNOWLEDGE_ASSET_BASE_PATH must not be empty".to_owned());
}
if normalized_relative.is_empty() {
return Err("Vite manifest contains an empty asset path".to_owned());
}
if normalized_base == "/" {
return Ok(format!("/{normalized_relative}"));
}
return Ok(format!("{}/{normalized_relative}", normalized_base.trim_end_matches('/')));
}
fn local_static_mount_path(base: &str) -> Option<String> {
let normalized = base.trim();
if !normalized.starts_with('/') {
return None;
}
if normalized == "/" {
return Some("/assets".to_owned());
}
return Some(format!("{}/assets", normalized.trim_end_matches('/')));
}

View File

@@ -0,0 +1,27 @@
// file: crates/knowledge-web-lib/src/lib.rs
// version: 1
//! Shared Actix Web and Maud composition for the three Knowledge server launchers.
//!
//! The crate owns the minimal HTTP runtime, Maud rendering and Vite asset resolution shared by
//! PGR, HOWTOs and snippets. It does not own authentication, administration or persistence.
mod assets;
mod page;
mod runtime;
mod site;
/// Start one configured Knowledge Actix Web server.
pub use self::runtime::run_server;
/// Identify the public Knowledge facade owned by one server launcher.
pub use self::site::ServerSite;
/// Internal asset description shared by the renderer and runtime.
pub(crate) use self::assets::ViteAssets;
/// Internal loader that resolves development or production assets from environment configuration.
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

@@ -0,0 +1,52 @@
// file: crates/knowledge-web-lib/src/page.rs
// version: 1
//! Maud rendering adapted from the static template-lab contract.
/// Render the minimal public page shared by the three server launchers.
pub(crate) fn render_index_page(site: crate::ServerSite, assets: &crate::ViteAssets) -> maud::Markup {
return maud::html! {
(maud::DOCTYPE)
html lang="fr" {
head {
meta charset="UTF-8";
meta name="viewport" content="width=device-width, initial-scale=1";
title { (site.page_title()) }
@for stylesheet in assets.stylesheets() {
link rel="stylesheet" href=(stylesheet);
}
}
body {
main class="container py-5 knowledge-template-shell" data-content-family=(family_slug(site.content_family())) {
header class="mb-5" {
p class="text-body-secondary mb-2" { "SaseDev Knowledge" }
h1 class="display-5" { (site.heading()) }
p class="lead mb-0" { (site.lead()) }
}
section class="row g-4" aria-label="Famille de contenu" {
div class="col-12" {
article class="card knowledge-family-card" {
div class="card-body" {
h2 class="h4" { (site.heading()) }
p class="card-text" { (site.lead()) }
}
}
}
}
}
@for script in assets.scripts() {
script type="module" src=(script) {}
}
}
}
};
}
fn family_slug(family: knowledge_domain_lib::ContentFamily) -> &'static str {
match family {
knowledge_domain_lib::ContentFamily::Pgr => return "pgr",
knowledge_domain_lib::ContentFamily::Howto => return "howtos",
knowledge_domain_lib::ContentFamily::Snippet => return "snippets",
knowledge_domain_lib::ContentFamily::ProjectLocal => return "project-local",
}
}

View File

@@ -0,0 +1,73 @@
// file: crates/knowledge-web-lib/src/runtime.rs
// version: 1
//! Shared Actix Web process composition.
struct ServerState {
site: crate::ServerSite,
assets: crate::ViteAssets,
}
/// Start one Knowledge HTTP server using environment-owned bind and asset configuration.
///
/// `bind_environment` names the launcher-specific environment variable containing the socket
/// address, for example `127.0.0.1:18081`. The function never chooses a deployment address.
pub fn run_server(site: crate::ServerSite, bind_environment: &str) -> std::io::Result<()> {
let bind_address = match std::env::var(bind_environment) {
Ok(value) => {
if value.trim().is_empty() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{bind_environment} must not be empty")));
}
value
},
Err(_) => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{bind_environment} must be configured")));
},
};
let assets = match crate::load_assets_from_environment() {
Ok(value) => value,
Err(error) => return Err(error),
};
let static_assets_directory = match assets.static_assets_directory() {
Some(value) => Some(value.to_path_buf()),
None => None,
};
let static_mount_path = match assets.static_mount_path() {
Some(value) => Some(value.to_owned()),
None => None,
};
let state = actix_web::web::Data::new(ServerState { site, assets });
return actix_web::rt::System::new().block_on(async move {
let server = actix_web::HttpServer::new(move || {
let app_state = state.clone();
let static_directory = static_assets_directory.clone();
let static_mount = static_mount_path.clone();
return actix_web::App::new()
.app_data(app_state)
.route("/", actix_web::web::get().to(index))
.route("/healthz", actix_web::web::get().to(health))
.configure(move |configuration| {
configure_static_assets(configuration, static_mount.as_deref(), static_directory.as_deref());
});
});
let bound = match server.bind(bind_address.as_str()) {
Ok(value) => value,
Err(error) => return Err(error),
};
return bound.run().await;
});
}
async fn index(state: actix_web::web::Data<ServerState>) -> maud::Markup {
return crate::render_index_page(state.site, &state.assets);
}
async fn health() -> actix_web::HttpResponse {
return actix_web::HttpResponse::Ok().content_type("text/plain; charset=utf-8").body("ok");
}
fn configure_static_assets(configuration: &mut actix_web::web::ServiceConfig, mount_path: Option<&str>, directory: Option<&std::path::Path>) {
if let (Some(mount), Some(path)) = (mount_path, directory) {
configuration.service(actix_files::Files::new(mount, path.to_path_buf()));
}
}

View File

@@ -0,0 +1,53 @@
// file: crates/knowledge-web-lib/src/site.rs
// version: 1
//! Public facade identities used by the shared Web runtime.
/// Public Knowledge facade served by one launcher.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ServerSite {
/// Normative PGR facade.
Pgr,
/// Procedural HOWTOs facade.
Howtos,
/// Contextual snippets facade.
Snippets,
}
impl crate::ServerSite {
/// Return the domain content family associated with this facade.
pub fn content_family(self) -> knowledge_domain_lib::ContentFamily {
match self {
crate::ServerSite::Pgr => return knowledge_domain_lib::ContentFamily::Pgr,
crate::ServerSite::Howtos => return knowledge_domain_lib::ContentFamily::Howto,
crate::ServerSite::Snippets => return knowledge_domain_lib::ContentFamily::Snippet,
}
}
/// Return the page title used by the foundation renderer.
pub fn page_title(self) -> &'static str {
match self {
crate::ServerSite::Pgr => return "SaseDev Knowledge — PGR",
crate::ServerSite::Howtos => return "SaseDev Knowledge — HOWTOs",
crate::ServerSite::Snippets => return "SaseDev Knowledge — Snippets",
}
}
/// Return the main heading used by the foundation renderer.
pub fn heading(self) -> &'static str {
match self {
crate::ServerSite::Pgr => return "Project Governance Rules",
crate::ServerSite::Howtos => return "HOWTOs",
crate::ServerSite::Snippets => return "Snippets",
}
}
/// Return the short explanatory lead used by the foundation renderer.
pub fn lead(self) -> &'static str {
match self {
crate::ServerSite::Pgr => return "Connaissance normative de gouvernance.",
crate::ServerSite::Howtos => return "Guides procéduraux et explicatifs.",
crate::ServerSite::Snippets => return "Fragments courts et contextualisés.",
}
}
}

View File

@@ -0,0 +1,72 @@
// file: crates/knowledge-web-lib/src/unit_tests.rs
// version: 1
//! Unit tests for shared Web composition primitives.
#[test]
fn development_assets_include_vite_client_before_main_entry() {
let assets = match crate::ViteAssets::development("http://127.0.0.1:5173/") {
Ok(value) => value,
Err(error) => {
assert!(error.is_empty(), "unexpected development asset error: {error}");
return;
},
};
assert_eq!(assets.scripts().len(), 2);
assert_eq!(assets.scripts()[0], "http://127.0.0.1:5173/@vite/client");
assert_eq!(assets.scripts()[1], "http://127.0.0.1:5173/src/main.ts");
assert!(assets.stylesheets().is_empty());
assert!(assets.static_assets_directory().is_none());
assert!(assets.static_mount_path().is_none());
}
#[test]
fn production_manifest_resolves_hashes_and_asset_base() {
let manifest = r#"{
"src/main.ts": {
"file": "assets/main-abc.js",
"css": ["assets/main-def.css"],
"isEntry": true
}
}"#;
let assets = match crate::ViteAssets::from_manifest_text(manifest, "/", std::path::PathBuf::from("/tmp/knowledge-assets")) {
Ok(value) => value,
Err(error) => {
assert!(error.is_empty(), "unexpected manifest error: {error}");
return;
},
};
assert_eq!(assets.scripts().len(), 1);
assert_eq!(assets.scripts()[0], "/assets/main-abc.js");
assert_eq!(assets.stylesheets().len(), 1);
assert_eq!(assets.stylesheets()[0], "/assets/main-def.css");
assert_eq!(assets.static_assets_directory(), Some(std::path::Path::new("/tmp/knowledge-assets")));
assert_eq!(assets.static_mount_path(), Some("/assets"));
}
#[test]
fn manifest_requires_the_stable_main_entry() {
let manifest = r#"{"index.html": {"file": "assets/index.js", "isEntry": true}}"#;
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\""));
}

75
deltas/0.0.1/alpha.5.md Normal file
View File

@@ -0,0 +1,75 @@
<!-- file: deltas/0.0.1/alpha.5.md -->
<!-- version: 1 -->
# Delta 0.0.1-alpha.5 — composition Actix/Maud et manifest Vite
## Base requise
État `0.0.1-alpha.4.fix.2` committé et poussé après application du history `PASS`.
La version devient `0.0.1-alpha.5`.
## Scope
La tranche matérialise :
```text
knowledge-web-lib
knowledge-pgr-server
knowledge-howtos-server
knowledge-snippets-server
```
`knowledge-web-lib` possède la composition Actix Web/Maud partagée, la résolution des assets Vite et les deux routes publiques minimales `/` et `/healthz`. Les trois packages serveur restent des launchers minces.
## Dépendances Web Rust
Versions vérifiées à la préparation de la tranche :
```text
actix-web 4.15.0
actix-files 0.7.0
maud 0.27.0
serde 1.0.229
serde_json 1.0.151
```
Les dépendances restent dans `knowledge-web-lib`, seul owner qui les consomme directement.
## Configuration runtime
Chaque launcher exige sa variable de bind :
```text
KNOWLEDGE_PGR_BIND_ADDRESS
KNOWLEDGE_HOWTOS_BIND_ADDRESS
KNOWLEDGE_SNIPPETS_BIND_ADDRESS
```
La source d'assets est exclusive :
```text
KNOWLEDGE_VITE_DEV_ORIGIN
ou
KNOWLEDGE_VITE_MANIFEST_PATH
```
En production `KNOWLEDGE_ASSET_BASE_PATH` peut remplacer le préfixe `/`.
Aucun origin Vite, port de déploiement ou hostname n'est codé en dur.
## Rendu et assets
Le rendu runtime Maud reprend les invariants de classes/assets utiles du template lab. En développement, il charge `@vite/client` avant `src/main.ts`. En production, il lit `.vite/manifest.json`, résout l'entrée stable `src/main.ts` et sert le répertoire d'assets produit par Vite via Actix Files.
Une configuration absente, ambiguë ou un manifest invalide produit une erreur récupérable avant l'écoute réseau ; aucun `panic!`, `unwrap`, `expect` ou `?` n'est introduit.
## Hors scope
La tranche n'ajoute ni auth, ni administration fonctionnelle, ni routes admin/login, ni PostgreSQL, ni Gitea, ni publication Git, ni logging transverse sans besoin observé.
`Cargo.lock` n'est pas préfabriqué dans le delta : Cargo doit le régénérer dans l'environnement opérateur avec les nouvelles dépendances, puis le versionner.
Ne pas committe `alpha.5` avant production de son history à partir des preuves opérateur.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/001-PROJECT_PROFILE.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Profil du projet Knowledge
@@ -70,8 +70,8 @@ Les profils Java/Maven/Gradle, Android et autres technologies absentes ne sont p
| Rust stable / édition 2024 | `ADOPT` | profil Rust/Cargo PGR |
| Workspace Cargo | `ADOPT` | skeleton Rust/Cargo ; actif depuis `alpha.2` |
| `Cargo.lock` | `KEEP-LOCAL` | versionné : workspace applicatif déployable, résolution reproductible entre opérateur et livraisons |
| Actix Web | `KEEP-LOCAL` | trois launchers serveur distincts ; PGR ne possède pas de profil Actix dédié |
| Maud | `KEEP-LOCAL` | rendu SSR initial lorsque pertinent |
| Actix Web | `KEEP-LOCAL` | trois launchers matérialisés en `alpha.5` et composition partagée dans `knowledge-web-lib` |
| Maud | `KEEP-LOCAL` | rendu SSR commun matérialisé dans `knowledge-web-lib` en `alpha.5` |
| TypeScript | `ADOPT` | conforme au owner `530` et à l'intention locale |
| Package manager frontend | `KEEP-LOCAL` | npm sélectionné en `alpha.4` ; `package-lock.json` versionné |
| Vite | `KEEP-LOCAL` + owner `550` adopté | build/preview des templates et assets de référence ; manifest de production consommé par l’intégration Maud |

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Architecture de fondation Knowledge
@@ -264,7 +264,11 @@ En particulier, Knowledge ne doit pas hériter mécaniquement :
- d’une arborescence d’assets spécifique au corpus ;
- de dépendances TypeScript `@types/*` non justifiées par les packages réellement consommés.
`alpha.4` possède le frontend indépendant et la production du manifest. La clé stable `src/main.ts` constitue l'entrée d'asset commune de départ ; son entrée de manifest doit référencer les sorties JavaScript et CSS générées. `alpha.5` possède l’intégration Actix/Maud et doit décider si le loader d’assets devient une crate/lib partagée entre les trois serveurs.
`alpha.4` possède le frontend indépendant et la production du manifest. La clé stable `src/main.ts` constitue l'entrée d'asset commune de départ ; son entrée de manifest doit référencer les sorties JavaScript et CSS générées.
`alpha.5` matérialise `knowledge-web-lib` comme owner partagé de l'intégration Actix/Maud. La crate choisit entre un dev server configuré par `KNOWLEDGE_VITE_DEV_ORIGIN` et un manifest configuré par `KNOWLEDGE_VITE_MANIFEST_PATH`. En production elle dérive le répertoire `assets/` depuis le manifest et le sert sous le préfixe `/assets`, tandis que `KNOWLEDGE_ASSET_BASE_PATH` garde le préfixe d'URL configurable. Les erreurs de configuration/manifest restent récupérables et remontent avant le démarrage du listener.
Les trois packages `knowledge-pgr-server`, `knowledge-howtos-server` et `knowledge-snippets-server` sont des launchers minces : chacun sélectionne `ServerSite` et le nom de sa variable de bind, puis délègue au runtime commun. Ils ne possèdent aucune logique de rendu ou de manifest dupliquée.
## 10. Administration

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/001-V0_0_1_FOUNDATION_PLAN.md -->
<!-- version: 10 -->
<!-- version: 11 -->
# Plan 0.0.1 — fondation SaseDev Knowledge
@@ -181,10 +181,11 @@ La gate opérateur d'`alpha.4.fix.1` reste **FAIL**. L'hygiène Git est corrigé
L'audit structurel du fix contenait également une vérification trop large de tous les `*.d.ts` sous `Web/`, incluant `node_modules`; il doit être limité aux déclarations appartenant au source du projet.
`alpha.4.fix.2` ajoute donc `@types/node` sur la branche Node 24, expose les types `node`, ajoute `ESNext.Disposable` et corrige uniquement la gate structurelle associée. Aucun scope runtime n'est ajouté.
Sa gate opérateur est **PASS** : typecheck et build Vite passent, le manifest est valide, les gates Rust restent propres et le warning `@parcel/watcher` demeure seulement observable sans approbation automatique.
### alpha.5 — composition Actix/Maud et manifest Vite
Matérialiser le minimum Actix Web/Maud nécessaire pour prouver trois launchers minces et intégrer les assets produits par `alpha.4` :
Matérialiser `knowledge-web-lib` comme owner partagé de la composition Actix/Maud et trois launchers minces :
```text
knowledge-pgr-server
@@ -192,11 +193,15 @@ knowledge-howtos-server
knowledge-snippets-server
```
Ils doivent composer les mêmes contrats/bibliothèques lorsque la responsabilité est partagée et rester capables de recevoir des configurations de sources Git distinctes.
Les launchers ne possèdent que la sélection de façade et du nom de variable de bind. `knowledge-web-lib` possède les routes publiques minimales `/` et `/healthz`, le rendu Maud et l'intégration Vite.
Le HTML runtime est rendu avec Maud. En développement, la composition MAY cibler le dev server Vite configuré ; en production, elle MUST résoudre les assets hashés via le manifest Vite ou un mapping équivalent produit par la chaîne Web. Le pattern de `actix-auth-app` est à adapter sous les règles Knowledge, notamment sans `panic!` et sans origin/port codé en dur.
En développement, `KNOWLEDGE_VITE_DEV_ORIGIN` fournit l'origin et la page charge `@vite/client` puis `src/main.ts`. En production, `KNOWLEDGE_VITE_MANIFEST_PATH` fournit le manifest ; l'entrée stable `src/main.ts` est résolue vers ses assets hashés et le répertoire `assets/` est servi par Actix Files. `KNOWLEDGE_ASSET_BASE_PATH` garde le préfixe de production configurable. Configurer simultanément dev-origin et manifest est une erreur récupérable.
La tranche ne doit pas introduire admin complète, auth complète, persistance métier complète ni forge obligatoire.
Les binds sont exclusivement configurés par `KNOWLEDGE_PGR_BIND_ADDRESS`, `KNOWLEDGE_HOWTOS_BIND_ADDRESS` et `KNOWLEDGE_SNIPPETS_BIND_ADDRESS`. Aucun port de déploiement n'est codé en dur.
La gate doit couvrir les tests `knowledge-web-lib`, la compilation/Clippy des quatre nouvelles crates, le build Vite et un smoke HTTP production des trois launchers vérifiant `/healthz`, le rendu Maud et le service réel des assets hashés.
La tranche ne doit pas introduire admin complète, auth complète, persistance métier complète, logging transverse artificiel ni forge obligatoire.
### beta.1 — réconciliation de fondation

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
<!-- version: 10 -->
<!-- version: 11 -->
# Matrice de validation Knowledge
@@ -295,3 +295,24 @@ Le second fix Web doit prouver :
- gates Rust existantes toujours propres après le bump de version ;
- aucun serveur Actix/Maud, loader Rust du manifest, route runtime, migration PostgreSQL ou adapter de forge ajouté ;
- staging Git propre et outputs générés absents du dépôt.
## Gate de `0.0.1-alpha.5`
La composition serveur doit prouver :
- `VERSION = 0.0.1-alpha.5` et version workspace identique ;
- history `alpha.4.fix.2` `PASS` committé avant application ;
- workspace contenant `knowledge-web-lib` et exactement les trois nouveaux launchers attendus ;
- `knowledge-web-lib` comme unique owner Actix/Maud/Vite runtime partagé ;
- launchers dépendant de `knowledge-web-lib` sans dépendance directe Actix/Maud/Serde ;
- Actix Web `4.15.0`, Actix Files `0.7.0`, Maud `0.27.0`, Serde `1.0.229` et Serde JSON `1.0.151` dans le manifest de `knowledge-web-lib` ;
- aucun `panic!`, `unwrap`, `expect` ou `?` ajouté ;
- aucune origin/port Vite ou adresse de bind choisie par le code ;
- mode dev résolvant `@vite/client` puis `src/main.ts` depuis `KNOWLEDGE_VITE_DEV_ORIGIN` ;
- mode production résolvant `src/main.ts` depuis le manifest configuré et servant les fichiers `/assets/*` depuis le build réel ;
- routes runtime limitées à `/` et `/healthz` pour cette tranche ;
- aucune route admin/login, auth, PostgreSQL ou forge ajoutée ;
- `npm ci`, typecheck et build du template lab toujours `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 ;
- `Cargo.lock` mis à jour/versionné et staging propre.

95
history/0.0.1/alpha.5.md Normal file
View File

@@ -0,0 +1,95 @@
<!-- file: history/0.0.1/alpha.5.md -->
<!-- version: 1 -->
# History 0.0.1-alpha.5 — composition Actix/Maud et manifest Vite
## Statut
FAIL
La gate technique de `0.0.1-alpha.5` exécutée le `2026-09-28` est propre, mais la revue opérateur relève avant clôture une non-conformité avec la convention de layout Rust souhaitée pour Knowledge : les tests unitaires sont implémentés sous `src/unit_tests.rs` au lieu d'une arborescence `unit_tests/` séparée.
## Baseline
`alpha.4.fix.2` `PASS` a été historisé, committé et poussé avant application d'`alpha.5`.
```text
history/0.0.1/alpha.4.fix.2.md = PASS
commit 8f6f072
message 0.0.1-alpha.4.fix.2
branch master
push origin/master PASS
```
## Preuves techniques PASS
La structure `alpha.5` est propre.
La baseline Web passe :
```text
npm ci PASS
typecheck PASS
vite build PASS
```
Les audits Rust PGR passent sur 17 fichiers et 7 packages.
Cargo et Clippy strict passent.
Les tests workspace passent :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
Le manifest Vite est valide.
Les trois smokes HTTP production passent :
```text
knowledge-pgr-server PASS
knowledge-howtos-server PASS
knowledge-snippets-server PASS
```
`git diff --cached --check` est propre.
## Défaut de gouvernance/layout
Le staging contient notamment :
```text
crates/knowledge-web-lib/src/unit_tests.rs
```
et les trois crates introduites plus tôt possèdent également leur `src/unit_tests.rs`.
PGR `0.1.1` ne rend pas ce layout globalement obligatoire : il indique qu'un workspace **MAY** adopter une arborescence `unit_tests/` séparée et miroir de `src/`.
Knowledge sélectionne maintenant explicitement cette option comme convention locale obligatoire.
La tranche ne peut donc pas être fermée `PASS` tant que la workspace conserve ses tests unitaires dans `src/`.
## Décision
`0.0.1-alpha.5` est `FAIL`.
La transition autorisée est :
```text
0.0.1-alpha.5.fix.1
```
Le fix doit être global et non limité à `knowledge-web-lib` :
- déplacer les tests unitaires existants sous `unit_tests/` ;
- rattacher les fichiers de tests aux modules source avec `#[cfg(test)]` + `#[path = "..."]` ;
- réserver `tests/` aux tests d'intégration Cargo ;
- formaliser la convention dans `RULES.md` ;
- automatiser le contrôle du layout ;
- rejouer l'intégralité des gates Rust/Web/HTTP sans changement fonctionnel.