27 Commits

Author SHA1 Message Date
b382397244 0.0.3 2026-09-29 19:00:14 +02:00
9a1f73d57b 0.0.3-rel.1 2026-09-29 18:54:33 +02:00
0351aa045e 0.0.3-rc.1.fix.1 2026-09-29 18:08:50 +02:00
038b91864a 0.0.3-rc.1 2026-09-29 17:59:08 +02:00
282d59cfb7 0.0.3-beta.1 2026-09-29 15:04:19 +02:00
a87f3ba4ab 0.0.3-alpha.4 2026-09-29 13:57:45 +02:00
aca00e6595 0.0.3-alpha.3.fix.1 2026-09-29 13:33:00 +02:00
c3a825657d 0.0.3-alpha.3 2026-09-29 11:39:02 +02:00
88a63b06cc 0.0.3-alpha.2 2026-09-29 11:31:59 +02:00
3004e4b581 0.0.3-alpha.1.fix.2 2026-09-29 11:13:25 +02:00
dd63e8578a 0.0.3-alpha.1.fix.1 2026-09-29 10:16:04 +02:00
a74a19838f 0.0.3-alpha.1 2026-09-29 09:58:22 +02:00
183d354a67 0.0.2 2026-09-29 07:07:56 +02:00
3bb1ffebbe 0.0.2-rc.1 2026-09-29 06:56:51 +02:00
6de544f534 0.0.2-beta.1.fix.2 2026-09-29 06:34:10 +02:00
4b51098911 0.0.2-beta.1.fix.1 2026-09-29 06:24:50 +02:00
5cee2a65b0 0.0.2-beta.1 2026-09-29 06:10:29 +02:00
da5445354e 0.0.2-alpha.5 2026-09-29 05:59:03 +02:00
1fe39dfdeb 0.0.2-alpha.4.fix.1 2026-09-28 22:57:24 +02:00
3f8c795cc0 0.0.2-alpha.4 2026-09-28 22:42:48 +02:00
b9508b0228 0.0.2-alpha.3.fix.1 2026-09-28 22:30:15 +02:00
721e8a6d9c 0.0.2-alpha.3 2026-09-28 19:23:12 +02:00
38025266b9 0.0.2-alpha.2.fix.2 2026-09-28 17:06:45 +02:00
ffb47214af 0.0.2-alpha.2.fix.1 2026-09-28 16:25:25 +02:00
bdbd6a51a4 0.0.2-alpha.2 2026-09-28 16:16:55 +02:00
84c01eb8e8 0.0.2-alpha.1.fix.1 2026-09-28 16:03:46 +02:00
ab1e5fd652 0.0.2-alpha.1 2026-09-28 15:11:03 +02:00
111 changed files with 17316 additions and 308 deletions

View File

@@ -1,10 +1,32 @@
<!-- file: CHANGELOG.md -->
<!-- version: 12 -->
<!-- version: 18 -->
# Changelog SaseDev Knowledge
Ce fichier décrit les changements utiles des releases publiées. Les détails de tranches et preuves opérateur appartiennent aux plans, deltas et histories.
## 0.0.3
- ajoute un contrat Git d'écriture provider-neutral distinct de `GitRepository`, afin que les consommateurs lecture seule n'acquièrent aucune obligation d'écriture ;
- valide les chemins de mutation, l'identité de commit, les branches `refs/heads/...` et l'état distant attendu avant toute opération backend ;
- matérialise un worktree d'écriture isolé, applique create/update/delete sous confinement et refuse les traversées par symlink vers l'extérieur ;
- crée un commit candidat avec auteur/committer explicites, hooks/configuration Git ambiante neutralisés et parent vérifié exactement égal à la révision de base ;
- conserve les candidats sous une référence backend interne puis nettoie les worktrees temporaires contrôlés ;
- pousse une branche distante uniquement sous garde compare-and-exchange `--force-with-lease` correspondant à une révision attendue ou à une absence attendue ;
- traduit une divergence distante, y compris concurrente entre observation et push, en conflit récupérable sans écrasement silencieux ;
- conserve la forge optionnelle et laisse hors de cette release PostgreSQL, rebase/merge automatique, auth/admin, routes Web d'édition et workflow complet de proposition/revue/publication.
## 0.0.2
- matérialise un backend Git générique de lecture derrière `knowledge-git-api`, sans dépendance obligatoire à une forge ;
- prend en charge sources locales/remotes, mirror contrôlé, refresh avec pruning, résolution de référence vers commit et snapshots/worktrees détachés adressés par révision ;
- sécurise l'exécution Git sans shell, avec prompts interactifs désactivés, hooks neutralisés et erreurs publiques expurgées des localisations/credentials ;
- matérialise `knowledge-app-lib` comme owner du cas d'usage partagé de synchronisation `Changed` / `Unchanged`, avec révision précédente fournie par l'appelant et sans pseudo-persistance locale ;
- conserve `Periodic` comme origine du même cas d'usage sans imposer de scheduler concret ;
- rend Knowledge autonome après l'adoption ponctuelle PGR `v0.1.3` en possédant localement règles, templates et audits nécessaires au développement courant ;
- normalise les crates bibliothèques autour d'une façade crate-root mince et de modules propriétaires, sans modifier les API publiques validées ;
- conserve PostgreSQL, adapter de forge, écriture/publication Git, auth/admin complète, parsing final HOWTO/snippet, submodules/LFS et partage concurrent de la racine Git hors du scope de cette release.
## 0.0.1
- établit la gouvernance locale Knowledge à partir de PGR `v0.1.1` vérifiée ;

View File

@@ -1,11 +1,13 @@
# file: Cargo.toml
# version: 16
# version: 39
[workspace]
resolver = "3"
members = [
"crates/knowledge-domain-lib",
"crates/knowledge-app-lib",
"crates/knowledge-git-api",
"crates/knowledge-git-lib",
"crates/knowledge-sync-api",
"crates/knowledge-web-lib",
"crates/knowledge-pgr-server",
@@ -14,13 +16,15 @@ members = [
]
[workspace.package]
version = "0.0.1"
version = "0.0.3"
edition = "2024"
publish = false
repository = "https://git.sasedev.com/Sasedev/sasedev-knowledge"
[workspace.dependencies]
knowledge-sync-api = { path = "crates/knowledge-sync-api" }
knowledge-domain-lib = { path = "crates/knowledge-domain-lib" }
knowledge-git-api = { path = "crates/knowledge-git-api" }
knowledge-web-lib = { path = "crates/knowledge-web-lib" }
[workspace.lints.rust]

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 9 -->
<!-- version: 25 -->
# SaseDev Knowledge
@@ -11,7 +11,11 @@ Le dépôt applicatif retenu est `sasedev-knowledge`. Le nom court du projet et
La version courante est définie par [`VERSION`](VERSION).
`0.0.1` est la première stable de fondation de SaseDev Knowledge. Elle établit la gouvernance, les frontières d'architecture, le workspace Rust/Web, les premiers contrats provider-neutral et les validations nécessaires avant les capacités fonctionnelles des releases suivantes.
`0.0.1` est la première stable de fondation de SaseDev Knowledge. `0.0.2` est la stable du chemin Git générique de lecture, de l'autonomie normative locale et du cas d'usage partagé de synchronisation `Changed` / `Unchanged`.
`0.0.3` est la stable du chemin Git générique complet en lecture et écriture contrôlée : lecture/snapshot, création d'un commit candidat isolé, mutations confinées, identité explicite puis push compare-and-exchange protégé par `--force-with-lease` contre l'état distant attendu. Une branche peut être créée uniquement lorsque son absence est attendue ; toute divergence devient un conflit récupérable sans écrasement silencieux. Cette stable reste provider-neutral et n'introduit ni forge obligatoire, ni PostgreSQL, ni rebase/merge automatique, ni workflow produit complet de proposition/publication.
`knowledge-git-lib` implémente `knowledge-git-api::GitRepository` pour des sources physiques enregistrées : mirror contrôlé, refresh avec pruning, résolution d'une référence vers un commit et snapshot/worktree détaché adressé par révision. La création locale du candidat d'`alpha.3` reste séparée de cette sémantique de lecture. Les règles nécessaires au développement courant sont désormais possédées localement sous `docs/rules/`, et les futurs prompts Knowledge ne chargent pas PGR sauf réévaluation explicitement engagée. `knowledge-app-lib` compose maintenant refresh, résolution, comparaison avec la révision précédente et préparation de snapshot en un résultat explicite `Changed` / `Unchanged`, sans persister la dernière révision ni matérialiser de scheduler.
## Topologie produit de fondation
@@ -31,7 +35,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 sept packages : les trois frontières de fondation `knowledge-domain-lib`, `knowledge-git-api` et `knowledge-sync-api`, la composition partagée `knowledge-web-lib`, puis les trois launchers `knowledge-pgr-server`, `knowledge-howtos-server` et `knowledge-snippets-server`. Les deux crates `-api` restent passives et indépendantes d'Actix, Gitea et PostgreSQL.
Le workspace Rust matérialise neuf packages : les trois frontières de fondation `knowledge-domain-lib`, `knowledge-git-api` et `knowledge-sync-api`, le backend `knowledge-git-lib`, la couche application partagée `knowledge-app-lib`, la composition Web `knowledge-web-lib`, puis les trois launchers `knowledge-pgr-server`, `knowledge-howtos-server` et `knowledge-snippets-server`. Les deux crates `-api` restent passives et indépendantes d'Actix, Gitea et PostgreSQL.
## Frontend de référence et rendu Maud
@@ -47,6 +51,8 @@ La stable `0.0.1` matérialise `knowledge-web-lib` et les trois launchers `knowl
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.
La revue d'horizon de `0.0.3-beta.1` sépare désormais explicitement la construction produit des trois façades : fondation PostgreSQL partagée, modèles/projections de contenu, templates publics (dont pages de garde) pour PGR/HOWTOs/snippets, templates d'administration, intégration Actix Web et adaptation Maud des templates validés, puis workflow de gestion/proposition/revue/publication. Les templates restent d'abord développés et validés dans le laboratoire Web avant adaptation runtime en Maud ; cette trajectoire n'implémente encore aucune de ces phases pendant `0.0.3`.
## Invariants de fondation
- Git est la source autoritaire du contenu versionné.
@@ -67,13 +73,23 @@ Le runtime exige un bind configuré par launcher et l'une des deux sources d'ass
- [`docs/objectives/001-PROJECT_OBJECTIVES.md`](docs/objectives/001-PROJECT_OBJECTIVES.md) — objectifs et non-objectifs ;
- [`docs/architecture/001-PROJECT_PROFILE.md`](docs/architecture/001-PROJECT_PROFILE.md) — baseline PGR, profils/skeletons et matrice de décisions ;
- [`docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md`](docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md) — architecture de fondation multi-app Git/forge/PostgreSQL et frontières de contenu ;
- [`docs/architecture/003-PGR_ADOPTION_MAP.md`](docs/architecture/003-PGR_ADOPTION_MAP.md) — mapping final de l'adoption PGR `v0.1.3` vers les owners locaux Knowledge ;
- [`docs/rules/000-README.md`](docs/rules/000-README.md) — index des règles normatives locales autonomes ;
- [`docs/templates/001-NEW_SESSION_PROMPT.md`](docs/templates/001-NEW_SESSION_PROMPT.md) — pattern de reprise d'une future session sans consultation PGR automatique ;
- [`docs/studies/001-AUTH_ADMIN_CORPUS_STUDY.md`](docs/studies/001-AUTH_ADMIN_CORPUS_STUDY.md) — étude des deux corpus historiques auth/admin et décisions de réutilisation conceptuelle ;
- [`docs/studies/002-ACTIX_MAUD_VITE_CORPUS_STUDY.md`](docs/studies/002-ACTIX_MAUD_VITE_CORPUS_STUDY.md) — étude ciblée du pattern Vite manifest / Actix / Maud à adapter pour Knowledge ;
- [`docs/studies/003-V0_0_1_FOUNDATION_RECONCILIATION.md`](docs/studies/003-V0_0_1_FOUNDATION_RECONCILIATION.md) — réconciliation `beta.1`, état matérialisé, DEFER conservés et revue d'horizon ;
- [`docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md`](docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md) — comparaison processus Git / `gix` / `git2` et décision d'implémentation de lecture ;
- [`docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md`](docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md) — choix de la mission, stratégie de worktree d'écriture et invariants de push gardé ;
- [`docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md`](docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md) — réconciliation beta du chemin Git d'écriture et revue d'horizon produit PGR/HOWTOs/snippets ;
- [`docs/plans/001-V0_0_1_FOUNDATION_PLAN.md`](docs/plans/001-V0_0_1_FOUNDATION_PLAN.md) — plan historique de la fondation stable `0.0.1` ;
- [`docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md`](docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md) — plan historique de la stable `0.0.2`, gates et décisions de fermeture ;
- [`docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md`](docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md) — plan actif de l'écriture Git contrôlée et du push gardé ;
- [`docs/validation/001-VALIDATION_MATRIX.md`](docs/validation/001-VALIDATION_MATRIX.md) — matrice de validation ;
- [`prompts/001-V0_0_1_START_PROMPT.md`](prompts/001-V0_0_1_START_PROMPT.md) — prompt de démarrage autoritaire de la release.
- [`prompts/002-V0_0_2_START_PROMPT.md`](prompts/002-V0_0_2_START_PROMPT.md) — handoff préparé pour `0.0.2`, à utiliser uniquement après publication du tag `v0.0.1` et vérification post-tag `PASS`.
- [`prompts/002-V0_0_2_START_PROMPT.md`](prompts/002-V0_0_2_START_PROMPT.md) — handoff historique exécuté pour ouvrir `0.0.2` depuis la stable `v0.0.1` vérifiée.
- [`prompts/003-V0_0_3_START_PROMPT.md`](prompts/003-V0_0_3_START_PROMPT.md) — prompt local de reprise de `0.0.3` depuis la stable `v0.0.2` publiée et vérifiée.
- [`prompts/004-V0_0_4_START_PROMPT.md`](prompts/004-V0_0_4_START_PROMPT.md) — handoff préparé pendant la stabilisation de `0.0.3` pour ouvrir la fondation PostgreSQL partagée après publication réelle de `v0.0.3`.
## Langue et nomenclature

View File

@@ -1,31 +1,47 @@
<!-- file: ROADMAP.md -->
<!-- version: 6 -->
<!-- version: 12 -->
# Roadmap SaseDev Knowledge
Le roadmap ne réserve que les releases dont la mission est réellement engagée.
La roadmap montre la release effectivement en cours et un horizon borné des releases suivantes. Une release d'horizon reste prévisionnelle tant qu'elle n'est pas ouverte : son numéro, son ordre ou son scope peuvent encore être replanifiés par une revue d'horizon ultérieure.
Statuts : `( )` planifié, `(x)` terminé, `(d)` différé, `(c)` annulé.
Statuts : `( )` engagée/planifiée, `(x)` terminée, `(h)` horizon prévisionnel, `(d)` différée, `(c)` annulée.
## Fondation
- `(x) 0.0.1` — fondation Knowledge : gouvernance PGR, workspace à trois applications Actix Web, architecture Git/forge/PostgreSQL partagé, frontières PGR/HOWTO/snippet, contrat d'administration locale protégée sans UI/auth complète, workspace Rust, premiers contrats, baseline Web reproductible, rendu Maud et intégration réelle du manifest Vite, sans implémentation métier disproportionnée.
- `(x) 0.0.1` — fondation Knowledge : gouvernance locale, workspace à trois applications Actix Web, architecture Git/forge/PostgreSQL partagé, frontières PGR/HOWTO/snippet, baseline Web reproductible, rendu Maud et intégration réelle du manifest Vite, sans implémentation métier disproportionnée.
## Horizon engagé
## Releases livrées
- `( ) 0.0.2` — implémentation Git générique et synchronisation de lecture : backend provider-neutral derrière `knowledge-git-api`, configuration de sources locales/remotes, matérialisation locale reproductible, refresh/résolution de révision, snapshot/worktree de lecture et convergence des déclencheurs explicite/périodique vers un même cas d'usage, sans API de forge obligatoire, écriture/publication Git, PostgreSQL ni format final HOWTO/snippet.
- `(x) 0.0.2` — Git générique et synchronisation de lecture : backend provider-neutral, sources locales/remotes, mirror contrôlé, refresh/résolution de révision, snapshot/worktree de lecture et cas d'usage partagé `Changed` / `Unchanged`, sans forge obligatoire ni persistance PostgreSQL.
- `(x) 0.0.3` — écriture Git contrôlée et push gardé : contrat d'écriture séparé de la lecture, worktree candidat isolé, mutations confinées, commit à identité explicite et mise à jour d'une branche `refs/heads/...` uniquement lorsque son état distant correspond à l'attente fournie ; divergence = conflit récupérable, sans rebase/merge automatique ni forge obligatoire.
## Domaines futurs non numérotés
## Release engagée
Les thèmes suivants sont candidats à des releases ultérieures mais ne reçoivent pas encore de numéro :
- `( ) 0.0.4` — fondation PostgreSQL partagée : premier schéma réel et migrations, configuration logique persistante des sources, dernière révision synchronisée/indexée et état transversal minimal reconstructible ; Git reste autoritaire pour le contenu publié.
- écriture Git contrôlée, branches/propositions/rebase/push et workflow de publication ;
- état PostgreSQL partagé et migrations applicatives ;
- contribution/revue et administration ;
- publication/rendu des trois façades ;
- recherche et indexation avancées ;
- adapter Gitea puis autres forges selon besoin réel ;
- authentification/autorisation complète ;
- déploiement et opérations multi-domaines.
## Horizon produit après 0.0.4
La revue d'horizon `0.0.1-beta.1` engage uniquement `0.0.2`. Les autres thèmes restent volontairement non numérotés jusqu'à ce qu'un scope et un ordre réellement utiles soient décidés.
La revue `0.0.3-beta.1`, confirmée pendant la promotion stable, engage `0.0.4` comme prochaine mission. Les versions suivantes restent des horizons : elles explicitent la trajectoire des trois produits Web sans figer prématurément leurs détails.
- `(h) 0.0.5` — modèles de contenu et projections : corpus réels PGR/HOWTO/snippet, parsing/validation, métadonnées et relations utiles, projection locale lisible depuis Git + PostgreSQL et contrats de lecture nécessaires aux trois façades.
- `(h) 0.0.6` — templates publics des trois sites : développer et valider dans `Web/knowledge-template-lab` les pages de garde, navigation, listes, détails, recherche/filtrage visible et composants partagés pour PGR, HOWTOs et snippets, sans encore faire de ces templates le rendu runtime définitif.
- `(h) 0.0.7` — templates d'administration des trois sites : dashboards et surfaces de gestion propres à PGR, HOWTOs et snippets, formulaires/états de proposition et revue nécessaires au produit, toujours comme références Web validables avant adaptation serveur.
- `(h) 0.0.8` — lecture publique Actix Web / Maud : matérialiser les routes publiques réelles des trois serveurs, alimentées par Git/projections locales, et adapter en Maud les templates publics validés tout en conservant le pipeline Vite/assets commun.
- `(h) 0.0.9` — administration Actix Web / Maud et autorisation : matérialiser identité/session/capabilities réellement nécessaires, routes admin protégées par façade et adaptation Maud des templates d'administration validés.
- `(h) 0.0.10` — gestion, proposition, revue et publication : relier les interfaces d'administration aux cas d'usage d'édition, conserver la révision Git de base, gérer les conflits, produire/pousser la révision Git autoritaire puis réconcilier les projections PostgreSQL.
## Domaines ultérieurs à reclasser
Les thèmes suivants restent volontairement non numérotés ou peuvent être absorbés dans une release d'horizon seulement lorsqu'un besoin mesuré le justifie :
- recherche/indexation avancée au-delà des capacités PostgreSQL et des projections réellement nécessaires aux façades ;
- adapter Gitea puis autres forges et webhooks forge-only ;
- scheduler/jobs de synchronisation, indexation ou publication ;
- quatrième application d'administration transverse ;
- déploiement/opérations multi-domaines ;
- partage concurrent d'une même racine Git entre plusieurs processus ;
- support explicite Git LFS/submodules et politiques de rétention/cleanup avancées ;
- observabilité transversale ou service réseau supplémentaire sans ownership démontré.
Chaque revue d'horizon doit vérifier la prochaine mission et réévaluer au moins les deux releases qui la suivent. Elle peut conserver, réordonner, fusionner, scinder ou dénuméroter un horizon lorsque les preuves produit ou techniques le justifient.

View File

@@ -1,5 +1,5 @@
<!-- file: RULES.md -->
<!-- version: 9 -->
<!-- version: 15 -->
# Règles SaseDev Knowledge
@@ -9,50 +9,46 @@ Ce document gouverne le workspace applicatif `sasedev-knowledge` et sert de poin
Le contenu éditorial publié par PGR, HOWTOs et snippets vit dans des dépôts Git distincts et conserve sa propre gouvernance. Les règles de ce dépôt ne transforment pas ces contenus en sous-modules normatifs de l'application.
## Baseline PGR
## Provenance PGR et autonomie normative
La fondation `0.0.1` est cadrée à partir de la stable PGR `v0.1.1`, vérifiée par son rapport post-tag `publish.001` `PASS`.
La fondation stable `0.0.1` a été cadrée à partir de PGR `v0.1.1`. Cette référence reste historique et n'est pas réécrite rétroactivement.
Empreinte de l'archive PGR autoritaire utilisée pour ce cadrage :
Au démarrage explicite de `0.0.2`, Knowledge a réévalué PGR et retenu `v0.1.3` comme **source d'adoption** vérifiée par `pgr-0.1.3-publish.001-report.md = PASS` :
```text
SHA-256 caaf8f456f7f8520172ea7705fd0cf5c5bf0e942dc4a773410bcbe7fe4ce243a
tag v0.1.3
commit fe80dbbb737390a7ea99df27d979bd59ccfb86e3
SHA-256 f3a7dffd5fe0fd3542e6b5207fda0b94bca2bd05bd5a54542d0a08d635a5d6ee
```
Les contrats PGR adoptés sont instanciés localement dans ce dépôt. PGR n'est pas une dépendance runtime ou build de Knowledge.
Cette identité documente la provenance de l'adoption ; elle n'est pas une dépendance normative vivante. Une migration réussie doit laisser Knowledge autonome : les règles réellement retenues sont sélectionnées, adaptées et possédées localement par Knowledge. PGR n'est ni une dépendance runtime/build, ni un référentiel à charger automatiquement au début d'une version, d'une session ou d'une tranche.
## Owners PGR applicables
Le prompt `prompts/002-V0_0_2_START_PROMPT.md` reste historique : sa vérification de la stable PGR courante appartenait à l'adoption explicitement engagée au démarrage de `0.0.2`. Ce comportement ne devient pas le modèle des futurs prompts Knowledge.
La fondation retient les owners suivants dans leur scope réel :
Une future version de PGR n'a aucun effet automatique sur Knowledge. Elle n'est consultée que lorsqu'une réévaluation/adoption PGR est explicitement inscrite au scope d'une tranche ou d'une release. Cette réévaluation doit alors comparer la baseline locale Knowledge avec la version PGR choisie, classifier les décisions `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER`, puis matérialiser localement les décisions retenues avant de refermer l'adoption.
```text
200 gouvernance et lifecycle
210 workflow plan/delta/history/handoff
220 composition des skeletons pendant l'instanciation
300 documentation
400 validation proportionnée
500 famille langage/écosystème
510 Rust/Cargo
530 TypeScript
540 écosystème de packages JavaScript
550 Vite lorsque Vite est sélectionné
560 CSS/Sass
700 famille framework pour les frameworks réellement utilisés
```
`0.0.2-alpha.4` a terminé l'autonomie normative de l'adoption `v0.1.3` : les owners nécessaires sont possédés localement et les futures releases normales repartent de Knowledge seul.
Les skeletons `001-PROJECT_GOVERNANCE_FOUNDATION`, `002-RUST_CARGO_WORKSPACE` et `003-WEB_FRONTEND` sont les spécialisations retenues pour `0.0.1`, avec matérialisation progressive selon le plan.
## Owners normatifs locaux
Le profil `710-TAURI` n'est pas applicable au produit Web actuel. Aucun skeleton Tauri n'est adopté.
Le développement courant est gouverné par les owners Knowledge suivants :
Les profils Java/Maven/Gradle, Android et les autres technologies absentes du projet restent hors scope.
- [`docs/rules/200-GOVERNANCE.md`](docs/rules/200-GOVERNANCE.md) — baseline, autonomie et décisions ;
- [`docs/rules/210-WORKFLOW.md`](docs/rules/210-WORKFLOW.md) — plan/delta/history/fix/handoff ;
- [`docs/rules/300-DOCUMENTATION.md`](docs/rules/300-DOCUMENTATION.md) — structure documentaire, headers et prompts ;
- [`docs/rules/400-VALIDATION.md`](docs/rules/400-VALIDATION.md) — preuves proportionnées et autorité opérateur ;
- [`docs/rules/510-RUST_CARGO.md`](docs/rules/510-RUST_CARGO.md) — Rust/Cargo strict, façade crate-root et tests ;
- [`docs/rules/530-WEB_FRONTEND.md`](docs/rules/530-WEB_FRONTEND.md) — TypeScript/npm/Vite/Sass/Bootstrap/Maud.
Le mapping complet de provenance et de décisions `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER` est conservé dans [`docs/architecture/003-PGR_ADOPTION_MAP.md`](docs/architecture/003-PGR_ADOPTION_MAP.md). Les owners PGR non matérialisés localement ne sont pas des dépendances documentaires du développement quotidien.
## Priorité et décisions locales
Les obligations PGR réellement adoptées priment sur une préférence locale de Knowledge dans le même scope.
Les contrats issus de PGR et explicitement adoptés par Knowledge deviennent des règles **locales Knowledge** après adaptation/matérialisation. C'est leur formulation locale qui gouverne ensuite le projet ; PGR conserve uniquement la provenance de la décision.
Les choix propres au produit restent locaux lorsqu'aucun owner PGR plus spécifique ne les impose. C'est notamment le cas de l'architecture Git/forge/PostgreSQL, du choix Actix Web/Maud, de Bootstrap, de la topologie à trois applications Web et de la topologie des dépôts de contenu.
Aucun `OVERRIDE` PGR n'est établi par `0.0.1-alpha.1.fix.1`.
Aucun `OVERRIDE` PGR n'est requis par l'adoption `v0.1.3` de `0.0.2-alpha.1`.
Les décisions différées sont explicites dans [`docs/architecture/001-PROJECT_PROFILE.md`](docs/architecture/001-PROJECT_PROFILE.md). Une valeur différée ne doit pas être remplacée par un choix arbitraire provenant d'un autre projet.
@@ -60,7 +56,7 @@ Les décisions différées sont explicites dans [`docs/architecture/001-PROJECT_
`VERSION` est le repère canonique simple de la release unit applicative.
Knowledge adopte le workflow PGR :
Knowledge possède localement le workflow suivant, issu de l'adoption PGR :
```text
delta -> application -> gate opérateur -> history observé -> application du history -> commit/push -> delta suivant
@@ -94,7 +90,7 @@ knowledge-howtos-server
knowledge-snippets-server
```
Chaque package exécutable reste une couche de composition mince conformément au profil Rust/Cargo PGR. La logique partagée appartient aux crates `-lib` appropriées et ne doit pas être dupliquée entre les trois `main.rs`.
Chaque package exécutable reste une couche de composition mince conformément au contrat Rust/Cargo retenu localement par Knowledge. La logique partagée appartient aux crates `-lib` appropriées et ne doit pas être dupliquée entre les trois `main.rs`.
Les trois applications peuvent être déployées, redémarrées et dimensionnées séparément. Cette séparation de launchers ne constitue pas une autorisation à fragmenter arbitrairement le domaine en microservices.
@@ -118,6 +114,26 @@ Une proposition externe MUST conserver la révision Git de base et MUST NOT écr
La synchronisation générique MUST disposer d'une voie indépendante des webhooks. Les hooks et webhooks MAY accélérer une synchronisation qui converge vers le même cas d'usage interne.
Pour `0.0.2`, la première implémentation générique retenue utilise un processus `git` contrôlé derrière `knowledge-git-api`. Cette décision est locale à Knowledge et reste remplaçable derrière le contrat.
La configuration physique des sources Git et la matérialisation locale appartiennent à `knowledge-git-lib`, matérialisée en `0.0.2-alpha.2`. Les copies générées MUST résider sous une racine d'état explicitement fournie au backend, hors du dépôt applicatif et des dépôts éditoriaux. Ni une URL remote ni un `SourceId` brut ne doivent devenir directement un chemin de stockage.
`knowledge-git-lib` dérive une clé de stockage sûre à partir des octets UTF-8 du `SourceId` et ne réutilise jamais directement l'identifiant logique ou l'URL comme composant de chemin. Le backend MUST invoquer l'exécutable Git directement sans shell, avec arguments séparés et prompts interactifs désactivés. Les erreurs publiques du runner MUST NOT recopier les arguments de commande, l'URL source, stderr ou des credentials.
À partir de `0.0.2-alpha.3`, le backend enregistre explicitement les configurations physiques associées aux `SourceId` puis implémente `knowledge-git-api::GitRepository`. Le refresh MUST converger par le remote du mirror avec pruning ; une référence MUST être résolue avec vérification vers un commit avant d'être utilisée comme identité autoritaire. Les snapshots MUST être adressés par la révision résolue et un snapshot existant MUST être vérifié sur son `HEAD` et son état propre avant réutilisation. Une divergence ou un worktree sale est une erreur récupérable et ne déclenche aucune suppression silencieuse.
La racine d'état Git n'est pas un stockage métier. En `0.0.2`, elle est possédée exclusivement par une instance de backend/processus ; un partage concurrent inter-processus reste différé tant qu'un verrouillage dédié n'est pas matérialisé.
### Écriture Git contrôlée
`0.0.3` engage une capacité d'écriture Git générique derrière un contrat distinct du contrat de lecture. Elle reste indépendante d'une forge et ne transforme pas une proposition externe en publication directe.
Toute écriture MUST partir d'une révision de base explicite et utiliser un worktree contrôlé distinct des snapshots de lecture. Les chemins de mutation MUST rester relatifs au worktree ; les chemins absolus, composants `..`, accès `.git` et échappements hors racine sont interdits.
Les commits créés par le backend MUST utiliser une identité auteur/committer explicitement fournie et MUST neutraliser les hooks et prompts interactifs possédés par l'opération. La configuration Git globale de la machine ne constitue pas une source d'identité autoritaire.
Une mise à jour distante de `0.0.3` MUST viser une branche `refs/heads/...` et MUST être gardée par l'état distant attendu. Une divergence produit un conflit récupérable ; le backend MUST NOT effectuer un `push --force` non gardé, un rebase automatique ou un merge automatique pour masquer cette divergence.
## PostgreSQL
PostgreSQL est destiné à l'état applicatif : comptes, sessions, propositions, revues, audit applicatif, index, recherche, synchronisation et jobs.
@@ -169,7 +185,7 @@ Le package manager sélectionné est npm. Le fichier `package-lock.json` produit
Le package utilise TypeScript strict, Vite, Sass et Bootstrap. Bootstrap est un choix local Knowledge ; il ne devient pas une règle PGR universelle.
Le skeleton Web PGR `003-WEB_FRONTEND` et sa validation associée sont obligatoires pour `0.0.1`. Le frontend contrôlé doit utiliser la chaîne réellement sélectionnée TypeScript + Vite + Sass + Bootstrap pour valider indépendamment du backend les assets, comportements client et templates statiques de référence.
Le contrat Web courant est possédé par [`docs/rules/530-WEB_FRONTEND.md`](docs/rules/530-WEB_FRONTEND.md). Le frontend contrôlé utilise la chaîne réellement sélectionnée TypeScript + Vite + Sass + Bootstrap pour valider indépendamment du backend les assets, comportements client et templates statiques de référence.
Les templates statiques servent de support de conception, prévisualisation et validation navigateur. Ils MUST NOT devenir silencieusement une seconde implémentation runtime divergente : le HTML servi en production par les applications Actix est rendu avec Maud, et toute adaptation depuis le template de référence doit préserver explicitement les contrats utiles de structure, classes, attributs et assets.
@@ -177,7 +193,7 @@ 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 `/`.
`knowledge-web-lib` possède ce contrat partagé depuis la fondation `0.0.1`. 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.
@@ -185,9 +201,7 @@ Les trois launchers MUST recevoir leur socket de bind par configuration (`KNOWLE
### Politique `Cargo.lock`
PGR `v0.1.1` exige une décision explicite de lockfile selon le type de produit et le besoin de reproductibilité ; il n'impose pas universellement le versionnement de `Cargo.lock`.
Knowledge sélectionne localement la politique suivante :
Knowledge possède localement la politique de lockfile suivante :
```text
Cargo.lock généré localement par Cargo
@@ -201,7 +215,7 @@ Cette décision ne s'applique pas au frontend npm : `Web/knowledge-template-lab/
### Layout des tests
Knowledge sélectionne localement la convention `unit_tests/` séparée que PGR `0.1.1` autorise sans l'imposer globalement.
Knowledge sélectionne localement la convention `unit_tests/` séparée.
Pour toutes les crates Rust de ce workspace :
@@ -220,17 +234,15 @@ Exemple :
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.
`alpha.3` matérialise ainsi `knowledge-git-api` pour la frontière `GitRepository` et `knowledge-sync-api` pour la frontière `RepositoryEventSource`. Ces crates restent passives, sans runtime ni logging propre, et dépendent seulement des types de domaine nécessaires. Les implémentations Git, hooks, polling, webhooks ou forge restent hors de ces crates de contrat.
Le profil Rust strict PGR s'applique aux crates matérialisées, notamment Rust 2024, `unsafe` interdit par défaut, lints stricts, exports publics maîtrisés et chemins intra-crate explicites via la façade crate-root.
Le contrat Rust strict est possédé par [`docs/rules/510-RUST_CARGO.md`](docs/rules/510-RUST_CARGO.md). Pour toutes les crates bibliothèque, `src/lib.rs` reste une façade de documentation, modules et réexports ; les définitions de production résident dans des modules nommés selon leur responsabilité. Les crates `knowledge-git-api` et `knowledge-sync-api` appliquent ce contrat à partir de `0.0.2-alpha.4` sans changer leur API crate-root.
Les audits read-only PGR sélectionnés avec les premiers contrats publics sont `audit_rust_blank_lines.py`, `audit_rust_item_spacing.py`, `audit_rust_module_order.py`, `audit_rust_trait_import_blocks.py`, `audit_rust_reexport_blocks.py` et `audit_rust_export_completeness.py`. L'audit `const` n'est pas sélectionné globalement : il reste opt-in par fichier lorsqu'un ordre mécanique de constantes est réellement approprié.
Les audits Rust read-only possédés localement incluent `audit_rust_blank_lines.py`, `audit_rust_item_spacing.py`, `audit_rust_module_order.py`, `audit_rust_trait_import_blocks.py`, `audit_rust_reexport_blocks.py`, `audit_rust_crate_root_facades.py` et `audit_rust_export_completeness.py`. L'audit `const` n'est pas sélectionné globalement : il reste opt-in uniquement si un ordre mécanique de constantes devient réellement approprié.
## Secrets et configuration locale

View File

@@ -1 +1 @@
0.0.1
0.0.3

View File

@@ -0,0 +1,19 @@
# file: crates/knowledge-app-lib/Cargo.toml
# version: 1
[package]
name = "knowledge-app-lib"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-git-api.workspace = true
knowledge-sync-api.workspace = true
[dev-dependencies]
knowledge-domain-lib.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,14 @@
// file: crates/knowledge-app-lib/src/lib.rs
// version: 1
//! Shared application use cases for SaseDev Knowledge.
//!
//! This crate composes provider-neutral application contracts. It owns no Git process, scheduler,
//! database, HTTP runtime or forge integration.
mod repository_sync;
/// Result of reconciling one repository-backed content source.
pub use self::repository_sync::RepositorySyncOutcome;
/// Reconcile one source through the provider-neutral Git repository contract.
pub use self::repository_sync::synchronize_repository;

View File

@@ -0,0 +1,89 @@
// file: crates/knowledge-app-lib/src/repository_sync.rs
// version: 1
/// Result of reconciling one repository-backed content source.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RepositorySyncOutcome {
/// The observed revision matches the revision already known by the caller.
Unchanged {
/// Authoritative revision observed after refresh and resolution.
revision: knowledge_git_api::GitRevision,
},
/// The observed revision differs and a read snapshot has been prepared.
Changed {
/// Authoritative revision observed after refresh and resolution.
revision: knowledge_git_api::GitRevision,
/// Read-only snapshot prepared for the new authoritative revision.
snapshot: knowledge_git_api::GitSnapshot,
},
}
impl crate::RepositorySyncOutcome {
/// Return the authoritative revision observed by reconciliation.
pub fn revision(&self) -> &knowledge_git_api::GitRevision {
match self {
crate::RepositorySyncOutcome::Unchanged { revision } => return revision,
crate::RepositorySyncOutcome::Changed { revision, snapshot: _ } => return revision,
}
}
/// Return the prepared snapshot when reconciliation detected a change.
pub fn snapshot(&self) -> Option<&knowledge_git_api::GitSnapshot> {
match self {
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return None,
crate::RepositorySyncOutcome::Changed { revision: _, snapshot } => return Some(snapshot),
}
}
/// Return whether reconciliation detected a new authoritative revision.
pub fn is_changed(&self) -> bool {
match self {
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return false,
crate::RepositorySyncOutcome::Changed { revision: _, snapshot: _ } => return true,
}
}
}
/// Reconcile one source through the provider-neutral Git repository contract.
///
/// The signal cause does not change the reconciliation path: explicit, periodic and external
/// signals all request the same authoritative refresh/resolve/compare operation. The caller owns
/// persistence of the previous revision and any scheduling policy.
pub fn synchronize_repository<R>(
repository: &mut R,
signal: &knowledge_sync_api::RepositorySyncSignal,
reference: &knowledge_git_api::GitReference,
previous_revision: Option<&knowledge_git_api::GitRevision>,
) -> Result<crate::RepositorySyncOutcome, R::Error>
where
R: knowledge_git_api::GitRepository,
{
let refresh_result = knowledge_git_api::GitRepository::refresh(repository, signal.source_id());
match refresh_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let revision_result = knowledge_git_api::GitRepository::resolve_revision(repository, signal.source_id(), reference);
let revision = match revision_result {
Ok(value) => value,
Err(error) => return Err(error),
};
match previous_revision {
Some(previous) => {
if previous == &revision {
return Ok(crate::RepositorySyncOutcome::Unchanged { revision });
}
},
None => (),
}
let snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(repository, signal.source_id(), &revision);
let snapshot = match snapshot_result {
Ok(value) => value,
Err(error) => return Err(error),
};
return Ok(crate::RepositorySyncOutcome::Changed { revision, snapshot });
}
#[cfg(test)]
#[path = "../unit_tests/repository_sync.rs"]
mod tests;

View File

@@ -0,0 +1,121 @@
// file: crates/knowledge-app-lib/unit_tests/repository_sync.rs
// version: 1
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct FakeRepositoryError;
struct FakeRepository {
observed_revision: knowledge_git_api::GitRevision,
reject_snapshot: bool,
}
impl FakeRepository {
fn new(observed_revision: &str, reject_snapshot: bool) -> FakeRepository {
return FakeRepository { observed_revision: knowledge_git_api::GitRevision::new(observed_revision), reject_snapshot };
}
}
impl knowledge_git_api::GitRepository for FakeRepository {
type Error = FakeRepositoryError;
fn refresh(&mut self, _source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> {
return Ok(());
}
fn resolve_revision(
&self,
_source_id: &knowledge_domain_lib::SourceId,
_reference: &knowledge_git_api::GitReference,
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
return Ok(self.observed_revision.clone());
}
fn prepare_snapshot(
&self,
_source_id: &knowledge_domain_lib::SourceId,
revision: &knowledge_git_api::GitRevision,
) -> Result<knowledge_git_api::GitSnapshot, Self::Error> {
if self.reject_snapshot {
return Err(FakeRepositoryError);
}
return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), std::path::PathBuf::from("/controlled/snapshot")));
}
}
fn source_id() -> Option<knowledge_domain_lib::SourceId> {
let result = knowledge_domain_lib::SourceId::new("snippets");
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn signal(cause: knowledge_sync_api::RepositorySyncCause) -> Option<knowledge_sync_api::RepositorySyncSignal> {
let id = match source_id() {
Some(value) => value,
None => return None,
};
return Some(knowledge_sync_api::RepositorySyncSignal::new(id, cause));
}
#[test]
fn changed_revision_prepares_snapshot_and_returns_observed_revision() {
let mut repository = FakeRepository::new("bbbb", false);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let previous = knowledge_git_api::GitRevision::new("aaaa");
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "bbbb");
assert!(outcome.snapshot().is_some());
return;
}
#[test]
fn unchanged_revision_does_not_prepare_snapshot() {
let mut repository = FakeRepository::new("bbbb", true);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let previous = knowledge_git_api::GitRevision::new("bbbb");
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(!outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "bbbb");
assert!(outcome.snapshot().is_none());
return;
}
#[test]
fn periodic_signal_uses_the_same_reconciliation_path() {
let mut repository = FakeRepository::new("cccc", false);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Periodic) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let result = crate::synchronize_repository(&mut repository, &request, &reference, None);
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "cccc");
assert!(outcome.snapshot().is_some());
return;
}

View File

@@ -1,57 +1,30 @@
// file: crates/knowledge-git-api/src/lib.rs
// version: 2
// version: 5
//! Provider-neutral contracts for Git-backed Knowledge sources.
//!
//! This crate deliberately contains no forge API, process runner, filesystem checkout or database
//! implementation. Concrete Git backends depend on this contract, not the reverse.
mod reference;
mod repository;
mod revision;
mod snapshot;
mod write;
/// Opaque Git reference requested by a caller, such as a branch, tag or object expression.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitReference(String);
impl crate::GitReference {
/// Create an opaque Git reference without imposing one provider's reference grammar.
pub fn new(value: &str) -> crate::GitReference {
return crate::GitReference(value.to_owned());
}
/// Return the reference text.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Resolved authoritative Git revision identity.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitRevision(String);
impl crate::GitRevision {
/// Create a revision identity returned by a concrete Git implementation.
pub fn new(value: &str) -> crate::GitRevision {
return crate::GitRevision(value.to_owned());
}
/// Return the revision identity as text.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
pub use self::reference::GitReference;
/// Provider-neutral operations required from a Git repository backend.
///
/// Implementations may use a local Git executable, a Rust Git library or another generic Git
/// mechanism. Forge-only capabilities do not belong to this trait.
pub trait GitRepository {
/// Implementation-specific recoverable error.
type Error;
/// Refresh the local view of one configured source when the backend requires it.
fn refresh(&mut self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error>;
/// Resolve one opaque reference to the authoritative revision used by synchronization.
fn resolve_revision(&self, source_id: &knowledge_domain_lib::SourceId, reference: &crate::GitReference) -> Result<crate::GitRevision, Self::Error>;
}
pub use self::repository::GitRepository;
/// Resolved authoritative Git revision identity.
pub use self::revision::GitRevision;
/// Read-only local materialization of one authoritative Git revision.
pub use self::snapshot::GitSnapshot;
/// Provider-neutral types and operations for controlled Git writes.
pub use self::write::{
GitBranch, GitCommitIdentity, GitFileMutation, GitMutationPath, GitPushOutcome, GitPushRequest, GitRemoteState, GitRepositoryWriter, GitWriteInputError,
GitWriteRequest,
};
#[cfg(test)]
#[path = "../unit_tests/lib.rs"]

View File

@@ -0,0 +1,18 @@
// file: crates/knowledge-git-api/src/reference.rs
// version: 2
/// Opaque Git reference requested by a caller, such as a branch, tag or object expression.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitReference(String);
impl crate::GitReference {
/// Create an opaque Git reference without imposing one provider's reference grammar.
pub fn new(value: &str) -> crate::GitReference {
return Self(value.to_owned());
}
/// Return the reference text.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}

View File

@@ -0,0 +1,20 @@
// file: crates/knowledge-git-api/src/repository.rs
// version: 1
/// Provider-neutral operations required from a Git repository backend.
///
/// Implementations may use a local Git executable, a Rust Git library or another generic Git
/// mechanism. Forge-only capabilities do not belong to this trait.
pub trait GitRepository {
/// Implementation-specific recoverable error.
type Error;
/// Refresh the local view of one configured source when the backend requires it.
fn refresh(&mut self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error>;
/// Resolve one opaque reference to the authoritative revision used by synchronization.
fn resolve_revision(&self, source_id: &knowledge_domain_lib::SourceId, reference: &crate::GitReference) -> Result<crate::GitRevision, Self::Error>;
/// Prepare or validate a read-only local snapshot for one resolved revision.
fn prepare_snapshot(&self, source_id: &knowledge_domain_lib::SourceId, revision: &crate::GitRevision) -> Result<crate::GitSnapshot, Self::Error>;
}

View File

@@ -0,0 +1,18 @@
// file: crates/knowledge-git-api/src/revision.rs
// version: 2
/// Resolved authoritative Git revision identity.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitRevision(String);
impl crate::GitRevision {
/// Create a revision identity returned by a concrete Git implementation.
pub fn new(value: &str) -> crate::GitRevision {
return Self(value.to_owned());
}
/// Return the revision identity as text.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}

View File

@@ -0,0 +1,26 @@
// file: crates/knowledge-git-api/src/snapshot.rs
// version: 1
/// Read-only local materialization of one authoritative Git revision.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitSnapshot {
path: std::path::PathBuf,
revision: crate::GitRevision,
}
impl crate::GitSnapshot {
/// Build a snapshot contract from a resolved revision and its local read root.
pub fn new(revision: crate::GitRevision, path: std::path::PathBuf) -> crate::GitSnapshot {
return crate::GitSnapshot { path, revision };
}
/// Return the local root containing the materialized repository content.
pub fn path(&self) -> &std::path::Path {
return self.path.as_path();
}
/// Return the authoritative revision represented by the snapshot.
pub fn revision(&self) -> &crate::GitRevision {
return &self.revision;
}
}

View File

@@ -0,0 +1,319 @@
// file: crates/knowledge-git-api/src/write.rs
// version: 1
/// Invalid value supplied to the provider-neutral Git write contract.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GitWriteInputError {
/// A mutation path is empty or contains only whitespace.
EmptyMutationPath,
/// A mutation path is absolute or uses a platform-specific separator.
AbsoluteMutationPath,
/// A mutation path contains an empty, current-directory or parent-directory component.
UnnormalizedMutationPath,
/// A mutation path addresses Git's private `.git` area.
ProtectedGitPath,
/// A commit identity name is empty or contains a forbidden line break or NUL byte.
InvalidCommitIdentityName,
/// A commit identity email is empty or contains a forbidden line break or NUL byte.
InvalidCommitIdentityEmail,
/// A commit message is empty or contains a NUL byte.
InvalidCommitMessage,
/// A write request does not contain any file mutation.
NoMutations,
/// More than one mutation targets the same normalized path.
DuplicateMutationPath,
/// A target branch is not a valid full `refs/heads/...` Git reference.
InvalidBranch,
}
impl std::fmt::Display for crate::GitWriteInputError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
crate::GitWriteInputError::EmptyMutationPath => return formatter.write_str("Git mutation path must not be empty"),
crate::GitWriteInputError::AbsoluteMutationPath => return formatter.write_str("Git mutation path must be relative and use '/' separators"),
crate::GitWriteInputError::UnnormalizedMutationPath => return formatter.write_str("Git mutation path must be normalized"),
crate::GitWriteInputError::ProtectedGitPath => return formatter.write_str("Git mutation path must not address .git"),
crate::GitWriteInputError::InvalidCommitIdentityName => return formatter.write_str("Git commit identity name is invalid"),
crate::GitWriteInputError::InvalidCommitIdentityEmail => return formatter.write_str("Git commit identity email is invalid"),
crate::GitWriteInputError::InvalidCommitMessage => return formatter.write_str("Git commit message is invalid"),
crate::GitWriteInputError::NoMutations => return formatter.write_str("Git write request must contain at least one mutation"),
crate::GitWriteInputError::DuplicateMutationPath => return formatter.write_str("Git write request must not mutate one path more than once"),
crate::GitWriteInputError::InvalidBranch => return formatter.write_str("Git target branch must be a valid refs/heads reference"),
}
}
}
impl std::error::Error for crate::GitWriteInputError {}
/// Normalized relative path that may be mutated inside an isolated Git write worktree.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitMutationPath(String);
impl crate::GitMutationPath {
/// Validate and build a provider-neutral repository-relative mutation path.
pub fn new(value: &str) -> Result<crate::GitMutationPath, crate::GitWriteInputError> {
if value.trim().is_empty() {
return Err(crate::GitWriteInputError::EmptyMutationPath);
}
if value.starts_with('/') || value.contains('\\') || crate::write::has_windows_drive_prefix(value) {
return Err(crate::GitWriteInputError::AbsoluteMutationPath);
}
if value.contains('\0') {
return Err(crate::GitWriteInputError::UnnormalizedMutationPath);
}
for component in value.split('/') {
if component.is_empty() || component == "." || component == ".." {
return Err(crate::GitWriteInputError::UnnormalizedMutationPath);
}
if component.eq_ignore_ascii_case(".git") {
return Err(crate::GitWriteInputError::ProtectedGitPath);
}
}
return Ok(Self(value.to_owned()));
}
/// Return the normalized repository-relative path.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// One file mutation requested for a candidate Git commit.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GitFileMutation {
/// Create or replace one file with the provided bytes.
Write {
/// Validated repository-relative file path.
path: crate::GitMutationPath,
/// Complete bytes that must become the file content.
content: Vec<u8>,
},
/// Delete one existing path from the candidate tree.
Delete {
/// Validated repository-relative file path.
path: crate::GitMutationPath,
},
}
impl crate::GitFileMutation {
/// Return the validated path targeted by this mutation.
pub fn path(&self) -> &crate::GitMutationPath {
match self {
crate::GitFileMutation::Write { path, .. } => return path,
crate::GitFileMutation::Delete { path } => return path,
}
}
}
/// Explicit author and committer identity used to create a candidate commit.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitCommitIdentity {
name: String,
email: String,
}
impl crate::GitCommitIdentity {
/// Validate and build an explicit Git commit identity.
pub fn new(name: &str, email: &str) -> Result<crate::GitCommitIdentity, crate::GitWriteInputError> {
if crate::write::invalid_identity_component(name) {
return Err(crate::GitWriteInputError::InvalidCommitIdentityName);
}
if crate::write::invalid_identity_component(email) {
return Err(crate::GitWriteInputError::InvalidCommitIdentityEmail);
}
return Ok(Self { name: name.to_owned(), email: email.to_owned() });
}
/// Return the explicit identity name.
pub fn name(&self) -> &str {
return self.name.as_str();
}
/// Return the explicit identity email.
pub fn email(&self) -> &str {
return self.email.as_str();
}
}
/// Validated full Git branch reference accepted as a remote write target.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitBranch(String);
impl crate::GitBranch {
/// Validate a full `refs/heads/...` branch reference.
pub fn new(value: &str) -> Result<crate::GitBranch, crate::GitWriteInputError> {
if !crate::write::valid_branch_reference(value) {
return Err(crate::GitWriteInputError::InvalidBranch);
}
return Ok(Self(value.to_owned()));
}
/// Return the validated full branch reference.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Expected or observed state of one remote branch.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GitRemoteState {
/// The branch does not exist.
Absent,
/// The branch resolves to the provided revision.
Revision(crate::GitRevision),
}
/// Request to build one candidate commit from an explicit base revision.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitWriteRequest {
base_revision: crate::GitRevision,
mutations: Vec<crate::GitFileMutation>,
identity: crate::GitCommitIdentity,
message: String,
}
impl crate::GitWriteRequest {
/// Validate and build a candidate-commit request.
pub fn new(
base_revision: crate::GitRevision,
mutations: Vec<crate::GitFileMutation>,
identity: crate::GitCommitIdentity,
message: &str,
) -> Result<crate::GitWriteRequest, crate::GitWriteInputError> {
if mutations.is_empty() {
return Err(crate::GitWriteInputError::NoMutations);
}
if message.trim().is_empty() || message.contains('\0') {
return Err(crate::GitWriteInputError::InvalidCommitMessage);
}
let mut paths = std::collections::HashSet::new();
for mutation in &mutations {
if !paths.insert(mutation.path().as_str()) {
return Err(crate::GitWriteInputError::DuplicateMutationPath);
}
}
return Ok(Self { base_revision, mutations, identity, message: message.to_owned() });
}
/// Return the resolved base revision from which the candidate must be created.
pub fn base_revision(&self) -> &crate::GitRevision {
return &self.base_revision;
}
/// Return the ordered mutations to apply in the isolated write worktree.
pub fn mutations(&self) -> &[crate::GitFileMutation] {
return self.mutations.as_slice();
}
/// Return the explicit author/committer identity.
pub fn identity(&self) -> &crate::GitCommitIdentity {
return &self.identity;
}
/// Return the candidate commit message.
pub fn message(&self) -> &str {
return self.message.as_str();
}
}
/// Request to compare-and-exchange one remote branch to a candidate revision.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitPushRequest {
target_branch: crate::GitBranch,
expected_remote: crate::GitRemoteState,
candidate_revision: crate::GitRevision,
}
impl crate::GitPushRequest {
/// Build a guarded remote update request from already validated values.
pub fn new(target_branch: crate::GitBranch, expected_remote: crate::GitRemoteState, candidate_revision: crate::GitRevision) -> crate::GitPushRequest {
return Self { target_branch, expected_remote, candidate_revision };
}
/// Return the target remote branch.
pub fn target_branch(&self) -> &crate::GitBranch {
return &self.target_branch;
}
/// Return the remote state that must still hold for the update to succeed.
pub fn expected_remote(&self) -> &crate::GitRemoteState {
return &self.expected_remote;
}
/// Return the candidate revision to publish.
pub fn candidate_revision(&self) -> &crate::GitRevision {
return &self.candidate_revision;
}
}
/// Result of one guarded remote branch update.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GitPushOutcome {
/// The target branch was updated to the candidate revision.
Updated(
/// Revision now expected at the updated target branch.
crate::GitRevision,
),
/// The target branch no longer matched the expected state.
Conflict {
/// Actual remote state observed when the guarded update was rejected.
actual: crate::GitRemoteState,
},
}
/// Provider-neutral write operations required from a Git repository backend.
///
/// This capability is intentionally separate from [`crate::GitRepository`] so read-only consumers
/// and fakes do not need to implement write methods. A concrete backend may implement this trait
/// independently from the read contract.
pub trait GitRepositoryWriter {
/// Implementation-specific recoverable error.
type Error;
/// Create a candidate commit without updating a remote branch.
fn create_candidate(&mut self, source_id: &knowledge_domain_lib::SourceId, request: &crate::GitWriteRequest) -> Result<crate::GitRevision, Self::Error>;
/// Attempt one guarded remote branch update for a previously created candidate revision.
fn push_candidate(&mut self, source_id: &knowledge_domain_lib::SourceId, request: &crate::GitPushRequest) -> Result<crate::GitPushOutcome, Self::Error>;
}
fn has_windows_drive_prefix(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 2 {
return false;
}
return bytes[0].is_ascii_alphabetic() && bytes[1] == b':';
}
fn invalid_identity_component(value: &str) -> bool {
return value.trim().is_empty() || value.contains('\0') || value.contains('\n') || value.contains('\r');
}
fn valid_branch_reference(value: &str) -> bool {
const PREFIX: &str = "refs/heads/";
let branch = match value.strip_prefix(PREFIX) {
Some(value) => value,
None => return false,
};
if branch.is_empty()
|| branch.starts_with('/')
|| branch.ends_with('/')
|| branch.ends_with('.')
|| branch.contains("..")
|| branch.contains("@{")
|| branch.contains("//")
{
return false;
}
for byte in branch.bytes() {
if byte <= 0x20 || byte == 0x7f || matches!(byte, b'~' | b'^' | b':' | b'?' | b'*' | b'[' | b'\\') {
return false;
}
}
for component in branch.split('/') {
if component.is_empty() || component.starts_with('.') || component.ends_with(".lock") {
return false;
}
}
return true;
}

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-git-api/unit_tests/lib.rs
// version: 1
// version: 4
#[derive(Debug, Eq, PartialEq)]
struct FakeGitError;
@@ -16,6 +16,24 @@ impl crate::GitRepository for FakeGitRepository {
fn resolve_revision(&self, source_id: &knowledge_domain_lib::SourceId, reference: &crate::GitReference) -> Result<crate::GitRevision, Self::Error> {
return Ok(crate::GitRevision::new(&format!("{}:{}", source_id.as_str(), reference.as_str())));
}
fn prepare_snapshot(&self, _source_id: &knowledge_domain_lib::SourceId, revision: &crate::GitRevision) -> Result<crate::GitSnapshot, Self::Error> {
return Ok(crate::GitSnapshot::new(revision.clone(), std::path::PathBuf::from("/controlled/snapshot")));
}
}
struct FakeGitWriter;
impl crate::GitRepositoryWriter for FakeGitWriter {
type Error = FakeGitError;
fn create_candidate(&mut self, _source_id: &knowledge_domain_lib::SourceId, request: &crate::GitWriteRequest) -> Result<crate::GitRevision, Self::Error> {
return Ok(crate::GitRevision::new(&format!("candidate:{}", request.base_revision().as_str())));
}
fn push_candidate(&mut self, _source_id: &knowledge_domain_lib::SourceId, request: &crate::GitPushRequest) -> Result<crate::GitPushOutcome, Self::Error> {
return Ok(crate::GitPushOutcome::Updated(request.candidate_revision().clone()));
}
}
#[test]
@@ -31,5 +49,148 @@ fn generic_repository_contract_does_not_require_a_forge() {
assert!(crate::GitRepository::refresh(&mut repository, &source_id).is_ok());
let revision_result = crate::GitRepository::resolve_revision(&repository, &source_id, &reference);
assert_eq!(revision_result, Ok(crate::GitRevision::new("local-pgr:refs/heads/master")));
let revision = match revision_result {
Ok(value) => value,
Err(_) => return,
};
let snapshot_result = crate::GitRepository::prepare_snapshot(&repository, &source_id, &revision);
assert_eq!(snapshot_result, Ok(crate::GitSnapshot::new(revision, std::path::PathBuf::from("/controlled/snapshot"))));
return;
}
#[test]
fn mutation_paths_are_relative_normalized_and_protect_git_metadata() {
let valid = crate::GitMutationPath::new("docs/rules/200-GOVERNANCE.md");
assert_eq!(valid.as_ref().map(crate::GitMutationPath::as_str), Ok("docs/rules/200-GOVERNANCE.md"));
assert_eq!(crate::GitMutationPath::new(""), Err(crate::GitWriteInputError::EmptyMutationPath));
assert_eq!(crate::GitMutationPath::new("/etc/passwd"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
assert_eq!(crate::GitMutationPath::new("C:/outside.txt"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
assert_eq!(crate::GitMutationPath::new("docs\\rules.md"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
assert_eq!(crate::GitMutationPath::new("docs/../RULES.md"), Err(crate::GitWriteInputError::UnnormalizedMutationPath));
assert_eq!(crate::GitMutationPath::new("docs/./RULES.md"), Err(crate::GitWriteInputError::UnnormalizedMutationPath));
assert_eq!(crate::GitMutationPath::new(".git/config"), Err(crate::GitWriteInputError::ProtectedGitPath));
assert_eq!(crate::GitMutationPath::new("nested/.GIT/config"), Err(crate::GitWriteInputError::ProtectedGitPath));
return;
}
#[test]
fn commit_identity_is_explicit_and_rejects_line_breaks() {
let identity = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
assert_eq!(identity.as_ref().map(crate::GitCommitIdentity::name), Ok("Knowledge Publisher"));
assert_eq!(identity.as_ref().map(crate::GitCommitIdentity::email), Ok("knowledge@example.invalid"));
assert_eq!(crate::GitCommitIdentity::new(" ", "knowledge@example.invalid"), Err(crate::GitWriteInputError::InvalidCommitIdentityName));
assert_eq!(
crate::GitCommitIdentity::new("Knowledge\nPublisher", "knowledge@example.invalid"),
Err(crate::GitWriteInputError::InvalidCommitIdentityName)
);
assert_eq!(crate::GitCommitIdentity::new("Knowledge Publisher", " "), Err(crate::GitWriteInputError::InvalidCommitIdentityEmail));
return;
}
#[test]
fn target_branch_is_limited_to_valid_heads_references() {
let branch = crate::GitBranch::new("refs/heads/knowledge/proposal-42");
assert_eq!(branch.as_ref().map(crate::GitBranch::as_str), Ok("refs/heads/knowledge/proposal-42"));
for invalid in [
"main",
"refs/tags/v1",
"refs/heads/",
"refs/heads/.hidden",
"refs/heads/topic..next",
"refs/heads/topic.lock",
"refs/heads/topic@{next",
"refs/heads/topic\\next",
"refs/heads/topic//next",
] {
assert_eq!(crate::GitBranch::new(invalid), Err(crate::GitWriteInputError::InvalidBranch), "{invalid}");
}
return;
}
#[test]
fn write_request_requires_mutations_unique_paths_and_message() {
let identity_result = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
let identity = match identity_result {
Ok(value) => value,
Err(_) => return,
};
let empty = crate::GitWriteRequest::new(crate::GitRevision::new("base"), Vec::new(), identity.clone(), "candidate");
assert_eq!(empty, Err(crate::GitWriteInputError::NoMutations));
let path_result = crate::GitMutationPath::new("content.md");
let path = match path_result {
Ok(value) => value,
Err(_) => return,
};
let duplicates =
vec![crate::GitFileMutation::Write { path: path.clone(), content: b"one".to_vec() }, crate::GitFileMutation::Delete { path: path.clone() }];
let duplicate_request = crate::GitWriteRequest::new(crate::GitRevision::new("base"), duplicates, identity.clone(), "candidate");
assert_eq!(duplicate_request, Err(crate::GitWriteInputError::DuplicateMutationPath));
let mutation = crate::GitFileMutation::Write { path, content: b"content".to_vec() };
let invalid_message = crate::GitWriteRequest::new(crate::GitRevision::new("base"), vec![mutation.clone()], identity.clone(), " ");
assert_eq!(invalid_message, Err(crate::GitWriteInputError::InvalidCommitMessage));
let request = crate::GitWriteRequest::new(crate::GitRevision::new("base"), vec![mutation], identity, "candidate");
assert!(request.is_ok());
return;
}
#[test]
fn push_request_models_absent_or_revision_remote_state() {
let branch_result = crate::GitBranch::new("refs/heads/proposals/example");
let branch = match branch_result {
Ok(value) => value,
Err(_) => return,
};
let candidate = crate::GitRevision::new("candidate");
let create = crate::GitPushRequest::new(branch.clone(), crate::GitRemoteState::Absent, candidate.clone());
assert_eq!(create.target_branch(), &branch);
assert_eq!(create.expected_remote(), &crate::GitRemoteState::Absent);
assert_eq!(create.candidate_revision(), &candidate);
let existing = crate::GitPushRequest::new(branch, crate::GitRemoteState::Revision(crate::GitRevision::new("old")), candidate);
assert_eq!(existing.expected_remote(), &crate::GitRemoteState::Revision(crate::GitRevision::new("old")));
return;
}
#[test]
fn write_contract_is_separate_from_read_contract_and_provider_neutral() {
let source_result = knowledge_domain_lib::SourceId::new("snippets-extra");
let source_id = match source_result {
Ok(value) => value,
Err(_) => return,
};
let path_result = crate::GitMutationPath::new("snippets/example.md");
let path = match path_result {
Ok(value) => value,
Err(_) => return,
};
let identity_result = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
let identity = match identity_result {
Ok(value) => value,
Err(_) => return,
};
let write_result = crate::GitWriteRequest::new(
crate::GitRevision::new("base"),
vec![crate::GitFileMutation::Write { path, content: b"snippet".to_vec() }],
identity,
"Add snippet",
);
let write_request = match write_result {
Ok(value) => value,
Err(_) => return,
};
let mut writer = FakeGitWriter;
let candidate_result = crate::GitRepositoryWriter::create_candidate(&mut writer, &source_id, &write_request);
assert_eq!(candidate_result, Ok(crate::GitRevision::new("candidate:base")));
let candidate = match candidate_result {
Ok(value) => value,
Err(_) => return,
};
let branch_result = crate::GitBranch::new("refs/heads/proposals/snippet");
let branch = match branch_result {
Ok(value) => value,
Err(_) => return,
};
let push_request = crate::GitPushRequest::new(branch, crate::GitRemoteState::Absent, candidate.clone());
let push_result = crate::GitRepositoryWriter::push_candidate(&mut writer, &source_id, &push_request);
assert_eq!(push_result, Ok(crate::GitPushOutcome::Updated(candidate)));
return;
}

View File

@@ -0,0 +1,16 @@
# file: crates/knowledge-git-lib/Cargo.toml
# version: 2
[package]
name = "knowledge-git-lib"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-domain-lib.workspace = true
knowledge-git-api.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,674 @@
// file: crates/knowledge-git-lib/src/backend.rs
// version: 4
//! Physical source configuration, generated-state layout and controlled Git process runner.
/// Explicit physical configuration for one controlled Git backend instance.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitBackendConfig {
git_executable: std::path::PathBuf,
state_root: std::path::PathBuf,
}
impl crate::GitBackendConfig {
/// Build a backend configuration using `git` from the process environment.
pub fn new(state_root: std::path::PathBuf) -> Result<crate::GitBackendConfig, crate::GitBackendError> {
return crate::GitBackendConfig::with_git_executable(state_root, std::path::PathBuf::from("git"));
}
/// Build a backend configuration with an explicit Git executable path.
pub fn with_git_executable(state_root: std::path::PathBuf, git_executable: std::path::PathBuf) -> Result<crate::GitBackendConfig, crate::GitBackendError> {
if state_root.as_os_str().is_empty() {
return Err(crate::GitBackendError::EmptyStateRoot);
}
if git_executable.as_os_str().is_empty() {
return Err(crate::GitBackendError::EmptyGitExecutable);
}
return Ok(crate::GitBackendConfig { git_executable, state_root });
}
/// Return the configured Git executable path.
pub fn git_executable(&self) -> &std::path::Path {
return self.git_executable.as_path();
}
/// Return the exclusive generated-state root owned by this backend instance.
pub fn state_root(&self) -> &std::path::Path {
return self.state_root.as_path();
}
/// Return the empty controlled hooks directory used to disable repository hooks.
pub fn hooks_path(&self) -> std::path::PathBuf {
return self.state_root.join("hooks-disabled");
}
/// Return the directory that contains controlled bare mirrors.
pub fn repositories_path(&self) -> std::path::PathBuf {
return self.state_root.join("repositories");
}
/// Return the directory reserved for revision-addressed snapshots.
pub fn snapshots_path(&self) -> std::path::PathBuf {
return self.state_root.join("snapshots");
}
/// Return the directory reserved for temporary isolated write worktrees.
pub fn write_worktrees_path(&self) -> std::path::PathBuf {
return self.state_root.join("write-worktrees");
}
/// Return the controlled empty global Git configuration path.
pub fn empty_global_config_path(&self) -> std::path::PathBuf {
return self.state_root.join("empty-global.gitconfig");
}
}
/// Coarse physical source kind used by the backend.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GitSourceKind {
/// Source is a path on the local filesystem.
Local,
/// Source is a Git remote location interpreted by the Git executable.
Remote,
}
/// Physical Git source configuration associated with one logical source identifier.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitSourceConfig {
location: std::ffi::OsString,
source_id: knowledge_domain_lib::SourceId,
source_kind: crate::GitSourceKind,
}
impl crate::GitSourceConfig {
/// Build a local Git source configuration.
pub fn local(source_id: knowledge_domain_lib::SourceId, path: std::path::PathBuf) -> Result<crate::GitSourceConfig, crate::GitBackendError> {
if path.as_os_str().is_empty() {
return Err(crate::GitBackendError::EmptySourceLocation);
}
return Ok(crate::GitSourceConfig { location: path.into_os_string(), source_id, source_kind: crate::GitSourceKind::Local });
}
/// Build a remote Git source configuration without interpreting provider-specific syntax.
pub fn remote(source_id: knowledge_domain_lib::SourceId, location: &str) -> Result<crate::GitSourceConfig, crate::GitBackendError> {
if location.trim().is_empty() {
return Err(crate::GitBackendError::EmptySourceLocation);
}
return Ok(crate::GitSourceConfig { location: std::ffi::OsString::from(location), source_id, source_kind: crate::GitSourceKind::Remote });
}
/// Return the logical source identifier associated with this physical source.
pub fn source_id(&self) -> &knowledge_domain_lib::SourceId {
return &self.source_id;
}
/// Return whether the source is local or remote.
pub fn source_kind(&self) -> crate::GitSourceKind {
return self.source_kind;
}
}
/// Path-safe storage key derived from a logical Knowledge source identifier.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GitSourceKey(String);
impl crate::GitSourceKey {
/// Derive a reversible collision-free key from the UTF-8 bytes of a logical source identifier.
pub fn from_source_id(source_id: &knowledge_domain_lib::SourceId) -> crate::GitSourceKey {
let mut value = String::from("source-");
for byte in source_id.as_str().as_bytes() {
value.push_str(format!("{:02x}", *byte).as_str());
}
return Self(value);
}
/// Return the path-safe key text.
pub fn as_str(&self) -> &str {
return self.0.as_str();
}
}
/// Result of ensuring that a controlled mirror exists.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GitMirrorStatus {
/// The mirror was created by the current call.
Created,
/// A valid existing mirror was reused.
Existing,
}
/// Description of one initialized or already existing controlled mirror.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitMirror {
repository_path: std::path::PathBuf,
source_key: crate::GitSourceKey,
status: crate::GitMirrorStatus,
}
impl crate::GitMirror {
/// Return the controlled bare-repository path.
pub fn repository_path(&self) -> &std::path::Path {
return self.repository_path.as_path();
}
/// Return the path-safe key associated with the logical source identifier.
pub fn source_key(&self) -> &crate::GitSourceKey {
return &self.source_key;
}
/// Return whether the mirror was newly created or already existed.
pub fn status(&self) -> crate::GitMirrorStatus {
return self.status;
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct GitCommandSpec {
arguments: Vec<std::ffi::OsString>,
environment: Vec<(std::ffi::OsString, std::ffi::OsString)>,
executable: std::path::PathBuf,
global_config_path: std::path::PathBuf,
hooks_path: std::path::PathBuf,
operation: crate::GitOperation,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct GitCommandOutput {
stdout: Vec<u8>,
}
trait GitCommandRunner {
fn run(&self, specification: &GitCommandSpec) -> Result<GitCommandOutput, crate::GitBackendError>;
}
struct SystemGitRunner;
impl GitCommandRunner for SystemGitRunner {
fn run(&self, specification: &GitCommandSpec) -> Result<GitCommandOutput, crate::GitBackendError> {
let mut command = std::process::Command::new(&specification.executable);
command.args(&specification.arguments);
command.env("GIT_TERMINAL_PROMPT", "0");
command.env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES");
command.env_remove("GIT_COMMON_DIR");
command.env_remove("GIT_CONFIG_PARAMETERS");
command.env_remove("GIT_DIR");
command.env_remove("GIT_INDEX_FILE");
command.env_remove("GIT_OBJECT_DIRECTORY");
command.env_remove("GIT_WORK_TREE");
command.env_remove("GIT_AUTHOR_NAME");
command.env_remove("GIT_AUTHOR_EMAIL");
command.env_remove("GIT_COMMITTER_NAME");
command.env_remove("GIT_COMMITTER_EMAIL");
command.env_remove("GIT_CONFIG_GLOBAL");
command.env_remove("GIT_CONFIG_NOSYSTEM");
command.env("GIT_CONFIG_GLOBAL", &specification.global_config_path);
command.env("GIT_CONFIG_NOSYSTEM", "1");
command.env("GIT_CONFIG_COUNT", "1");
command.env("GIT_CONFIG_KEY_0", "core.hooksPath");
command.env("GIT_CONFIG_VALUE_0", &specification.hooks_path);
for (name, value) in &specification.environment {
command.env(name, value);
}
let output_result = command.output();
let output = match output_result {
Ok(value) => value,
Err(_) => {
return Err(crate::GitBackendError::GitUnavailable { operation: specification.operation });
},
};
if !output.status.success() {
return Err(crate::GitBackendError::GitCommandFailed { operation: specification.operation, exit_code: output.status.code() });
}
return Ok(GitCommandOutput { stdout: output.stdout });
}
}
/// Controlled Git backend that owns configured sources and local read materialization.
pub struct GitBackend {
config: crate::GitBackendConfig,
runner: Box<dyn GitCommandRunner>,
sources: Vec<crate::GitSourceConfig>,
}
impl crate::GitBackend {
/// Create a backend using the configured real Git executable and no registered source yet.
pub fn new(config: crate::GitBackendConfig) -> crate::GitBackend {
return crate::GitBackend { config, runner: Box::new(SystemGitRunner), sources: Vec::new() };
}
#[cfg(test)]
fn with_runner(config: crate::GitBackendConfig, runner: Box<dyn GitCommandRunner>) -> crate::GitBackend {
return crate::GitBackend { config, runner, sources: Vec::new() };
}
/// Return the immutable backend configuration.
pub fn config(&self) -> &crate::GitBackendConfig {
return &self.config;
}
/// Register one physical source configuration for provider-neutral operations.
pub fn register_source(&mut self, source: crate::GitSourceConfig) -> Result<(), crate::GitBackendError> {
for existing in &self.sources {
if existing.source_id() == source.source_id() {
return Err(crate::GitBackendError::DuplicateSource);
}
}
self.sources.push(source);
return Ok(());
}
/// Derive the controlled mirror path without interpolating the raw source identifier.
pub fn repository_path(&self, source_id: &knowledge_domain_lib::SourceId) -> std::path::PathBuf {
let source_key = crate::GitSourceKey::from_source_id(source_id);
return self.config.repositories_path().join(format!("{}.git", source_key.as_str()));
}
/// Derive the revision-addressed snapshot path after validating the revision as a safe object identity.
pub fn snapshot_path(
&self,
source_id: &knowledge_domain_lib::SourceId,
revision: &knowledge_git_api::GitRevision,
) -> Result<std::path::PathBuf, crate::GitBackendError> {
if !crate::is_safe_revision(revision.as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
let source_key = crate::GitSourceKey::from_source_id(source_id);
return Ok(self.config.snapshots_path().join(source_key.as_str()).join(revision.as_str()));
}
/// Inspect one mirror when it already exists without creating it.
pub fn inspect_mirror(&self, source_id: &knowledge_domain_lib::SourceId) -> Result<Option<crate::GitMirror>, crate::GitBackendError> {
let source_key = crate::GitSourceKey::from_source_id(source_id);
let repository_path = self.repository_path(source_id);
if !repository_path.exists() {
return Ok(None);
}
let inspection_result = self.inspect_bare_repository(&repository_path);
match inspection_result {
Ok(()) => {
return Ok(Some(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Existing }));
},
Err(error) => return Err(error),
}
}
/// Ensure that one configured source has a valid controlled bare mirror.
pub fn ensure_mirror(&self, source: &crate::GitSourceConfig) -> Result<crate::GitMirror, crate::GitBackendError> {
let preparation_result = self.prepare_state_directories();
match preparation_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let source_key = crate::GitSourceKey::from_source_id(source.source_id());
let repository_path = self.repository_path(source.source_id());
if repository_path.exists() {
let inspection_result = self.inspect_bare_repository(&repository_path);
match inspection_result {
Ok(()) => {
return Ok(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Existing });
},
Err(error) => return Err(error),
}
}
let clone_result = self.clone_mirror(source, &repository_path);
match clone_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let inspection_result = self.inspect_bare_repository(&repository_path);
match inspection_result {
Ok(()) => {
return Ok(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Created });
},
Err(error) => return Err(error),
}
}
pub(crate) fn configured_source(&self, source_id: &knowledge_domain_lib::SourceId) -> Result<&crate::GitSourceConfig, crate::GitBackendError> {
for source in &self.sources {
if source.source_id() == source_id {
return Ok(source);
}
}
return Err(crate::GitBackendError::SourceNotConfigured);
}
pub(crate) fn prepare_state_directories(&self) -> Result<(), crate::GitBackendError> {
for path in [self.config.hooks_path(), self.config.repositories_path(), self.config.snapshots_path(), self.config.write_worktrees_path()] {
let create_result = std::fs::create_dir_all(path);
if create_result.is_err() {
return Err(crate::GitBackendError::StatePreparationFailed);
}
}
let config_path = self.config.empty_global_config_path();
if !config_path.exists() {
let write_result = std::fs::write(config_path, b"");
if write_result.is_err() {
return Err(crate::GitBackendError::StatePreparationFailed);
}
}
return Ok(());
}
pub(crate) fn run_controlled_git(
&self,
operation: crate::GitOperation,
arguments: Vec<std::ffi::OsString>,
environment: Vec<(std::ffi::OsString, std::ffi::OsString)>,
) -> Result<Vec<u8>, crate::GitBackendError> {
let specification = GitCommandSpec {
arguments,
environment,
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation,
};
let result = self.runner.run(&specification);
match result {
Ok(output) => return Ok(output.stdout),
Err(error) => return Err(error),
}
}
fn clone_mirror(&self, source: &crate::GitSourceConfig, repository_path: &std::path::Path) -> Result<(), crate::GitBackendError> {
let mut arguments = vec![std::ffi::OsString::from("clone"), std::ffi::OsString::from("--mirror")];
if source.source_kind == crate::GitSourceKind::Local {
arguments.push(std::ffi::OsString::from("--no-local"));
}
arguments.push(std::ffi::OsString::from("--"));
arguments.push(source.location.clone());
arguments.push(repository_path.as_os_str().to_os_string());
let specification = GitCommandSpec {
arguments,
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::CloneMirror,
};
let run_result = self.runner.run(&specification);
match run_result {
Ok(_) => return Ok(()),
Err(error) => return Err(error),
}
}
fn inspect_bare_repository(&self, repository_path: &std::path::Path) -> Result<(), crate::GitBackendError> {
let specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--is-bare-repository"),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::InspectMirror,
};
let run_result = self.runner.run(&specification);
let output = match run_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let stdout_result = std::str::from_utf8(&output.stdout);
let stdout = match stdout_result {
Ok(value) => value.trim(),
Err(_) => {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectMirror });
},
};
if stdout != "true" {
return Err(crate::GitBackendError::InvalidMirror);
}
return Ok(());
}
fn refresh_registered_source(&self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), crate::GitBackendError> {
let source_result = self.configured_source(source_id);
let source = match source_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let mirror_result = self.ensure_mirror(source);
let mirror = match mirror_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("remote"),
std::ffi::OsString::from("update"),
std::ffi::OsString::from("--prune"),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::RefreshMirror,
};
let run_result = self.runner.run(&specification);
match run_result {
Ok(_) => return Ok(()),
Err(error) => return Err(error),
}
}
fn resolve_registered_revision(
&self,
source_id: &knowledge_domain_lib::SourceId,
reference: &knowledge_git_api::GitReference,
) -> Result<knowledge_git_api::GitRevision, crate::GitBackendError> {
let configured_result = self.configured_source(source_id);
match configured_result {
Ok(_) => (),
Err(error) => return Err(error),
}
let mirror_result = self.inspect_mirror(source_id);
let mirror = match mirror_result {
Ok(Some(value)) => value,
Ok(None) => return Err(crate::GitBackendError::MirrorMissing),
Err(error) => return Err(error),
};
let revision_expression = format!("{}^{{commit}}", reference.as_str());
let specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from("--end-of-options"),
std::ffi::OsString::from(revision_expression),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::ResolveRevision,
};
let run_result = self.runner.run(&specification);
let output = match run_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let stdout_result = std::str::from_utf8(&output.stdout);
let stdout = match stdout_result {
Ok(value) => value.trim(),
Err(_) => {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::ResolveRevision });
},
};
if !crate::is_safe_revision(stdout) {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::ResolveRevision });
}
return Ok(knowledge_git_api::GitRevision::new(stdout));
}
fn prepare_registered_snapshot(
&self,
source_id: &knowledge_domain_lib::SourceId,
revision: &knowledge_git_api::GitRevision,
) -> Result<knowledge_git_api::GitSnapshot, crate::GitBackendError> {
let configured_result = self.configured_source(source_id);
match configured_result {
Ok(_) => (),
Err(error) => return Err(error),
}
if !crate::is_safe_revision(revision.as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
let mirror_result = self.inspect_mirror(source_id);
let mirror = match mirror_result {
Ok(Some(value)) => value,
Ok(None) => return Err(crate::GitBackendError::MirrorMissing),
Err(error) => return Err(error),
};
let snapshot_path_result = self.snapshot_path(source_id, revision);
let snapshot_path = match snapshot_path_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if snapshot_path.exists() {
let validation_result = self.validate_snapshot(&snapshot_path, revision);
match validation_result {
Ok(()) => return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), snapshot_path)),
Err(error) => return Err(error),
}
}
let parent = match snapshot_path.parent() {
Some(value) => value,
None => return Err(crate::GitBackendError::StatePreparationFailed),
};
let create_result = std::fs::create_dir_all(parent);
if create_result.is_err() {
return Err(crate::GitBackendError::StatePreparationFailed);
}
let specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("worktree"),
std::ffi::OsString::from("add"),
std::ffi::OsString::from("--detach"),
std::ffi::OsString::from("--"),
snapshot_path.as_os_str().to_os_string(),
std::ffi::OsString::from(revision.as_str()),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::CreateSnapshot,
};
let run_result = self.runner.run(&specification);
match run_result {
Ok(_) => (),
Err(error) => return Err(error),
}
let validation_result = self.validate_snapshot(&snapshot_path, revision);
match validation_result {
Ok(()) => return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), snapshot_path)),
Err(error) => return Err(error),
}
}
fn validate_snapshot(&self, snapshot_path: &std::path::Path, revision: &knowledge_git_api::GitRevision) -> Result<(), crate::GitBackendError> {
if !snapshot_path.is_dir() {
return Err(crate::GitBackendError::InvalidSnapshot);
}
let head_specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("-C"),
snapshot_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from("HEAD"),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::InspectSnapshot,
};
let head_result = self.runner.run(&head_specification);
let head_output = match head_result {
Ok(value) => value,
Err(_) => return Err(crate::GitBackendError::InvalidSnapshot),
};
let head_text_result = std::str::from_utf8(&head_output.stdout);
let head_text = match head_text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::InvalidSnapshot),
};
if head_text != revision.as_str() {
return Err(crate::GitBackendError::InvalidSnapshot);
}
let status_specification = GitCommandSpec {
arguments: vec![
std::ffi::OsString::from("-C"),
snapshot_path.as_os_str().to_os_string(),
std::ffi::OsString::from("status"),
std::ffi::OsString::from("--porcelain=v1"),
std::ffi::OsString::from("--untracked-files=all"),
],
environment: Vec::new(),
executable: self.config.git_executable().to_path_buf(),
global_config_path: self.config.empty_global_config_path(),
hooks_path: self.config.hooks_path(),
operation: crate::GitOperation::InspectSnapshot,
};
let status_result = self.runner.run(&status_specification);
let status_output = match status_result {
Ok(value) => value,
Err(_) => return Err(crate::GitBackendError::InvalidSnapshot),
};
if !status_output.stdout.is_empty() {
return Err(crate::GitBackendError::InvalidSnapshot);
}
return Ok(());
}
}
impl knowledge_git_api::GitRepository for crate::GitBackend {
type Error = crate::GitBackendError;
fn refresh(&mut self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> {
return self.refresh_registered_source(source_id);
}
fn resolve_revision(
&self,
source_id: &knowledge_domain_lib::SourceId,
reference: &knowledge_git_api::GitReference,
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
return self.resolve_registered_revision(source_id, reference);
}
fn prepare_snapshot(
&self,
source_id: &knowledge_domain_lib::SourceId,
revision: &knowledge_git_api::GitRevision,
) -> Result<knowledge_git_api::GitSnapshot, Self::Error> {
return self.prepare_registered_snapshot(source_id, revision);
}
}
pub(crate) fn is_safe_revision(value: &str) -> bool {
if value.is_empty() {
return false;
}
for character in value.chars() {
if !character.is_ascii_hexdigit() {
return false;
}
}
return true;
}
#[cfg(test)]
#[path = "../unit_tests/backend.rs"]
mod tests;

View File

@@ -0,0 +1,150 @@
// file: crates/knowledge-git-lib/src/error.rs
// version: 5
//! Recoverable errors for controlled Git process execution.
/// Git operation category safe to expose in diagnostics.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GitOperation {
/// Clone one configured source into a controlled mirror.
CloneMirror,
/// Inspect whether a controlled repository is a valid bare mirror.
InspectMirror,
/// Refresh refs in one controlled mirror.
RefreshMirror,
/// Resolve one configured reference to a commit revision.
ResolveRevision,
/// Materialize one detached revision-addressed snapshot.
CreateSnapshot,
/// Validate an existing detached snapshot before reuse.
InspectSnapshot,
/// Create one isolated detached worktree for candidate construction.
CreateWriteWorktree,
/// Stage the requested candidate mutations.
StageCandidate,
/// Inspect the candidate index, commit or parent relationship.
InspectCandidate,
/// Create the candidate commit with explicit identity.
CommitCandidate,
/// Retain the detached candidate commit under an internal backend reference.
PreserveCandidate,
/// Remove one backend-owned candidate worktree.
RemoveWriteWorktree,
/// Observe the current state of one remote branch.
InspectRemote,
/// Push one candidate revision with an explicit compare-and-exchange guard.
PushCandidate,
}
impl std::fmt::Display for crate::GitOperation {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
crate::GitOperation::CloneMirror => return formatter.write_str("clone mirror"),
crate::GitOperation::InspectMirror => return formatter.write_str("inspect mirror"),
crate::GitOperation::RefreshMirror => return formatter.write_str("refresh mirror"),
crate::GitOperation::ResolveRevision => return formatter.write_str("resolve revision"),
crate::GitOperation::CreateSnapshot => return formatter.write_str("create snapshot"),
crate::GitOperation::InspectSnapshot => return formatter.write_str("inspect snapshot"),
crate::GitOperation::CreateWriteWorktree => return formatter.write_str("create write worktree"),
crate::GitOperation::StageCandidate => return formatter.write_str("stage candidate"),
crate::GitOperation::InspectCandidate => return formatter.write_str("inspect candidate"),
crate::GitOperation::CommitCandidate => return formatter.write_str("commit candidate"),
crate::GitOperation::PreserveCandidate => return formatter.write_str("preserve candidate"),
crate::GitOperation::RemoveWriteWorktree => return formatter.write_str("remove write worktree"),
crate::GitOperation::InspectRemote => return formatter.write_str("inspect remote branch"),
crate::GitOperation::PushCandidate => return formatter.write_str("push guarded candidate"),
}
}
}
/// Recoverable error returned by the controlled Git backend.
///
/// Process failures intentionally retain only an operation category and exit code. Source URLs,
/// command arguments, stderr and credentials are not embedded in this public error.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GitBackendError {
/// The configured Git executable path is empty.
EmptyGitExecutable,
/// The configured state root is empty.
EmptyStateRoot,
/// A local source path or remote source string is empty.
EmptySourceLocation,
/// One logical source identifier was registered more than once in the same backend.
DuplicateSource,
/// No physical source configuration is registered for the requested logical source.
SourceNotConfigured,
/// The backend could not prepare its generated-state directories.
StatePreparationFailed,
/// The Git executable could not be started for the requested operation.
GitUnavailable {
/// Operation that could not start.
operation: crate::GitOperation,
},
/// Git returned a non-success status for the requested operation.
GitCommandFailed {
/// Operation that failed.
operation: crate::GitOperation,
/// Process exit code when the operating system exposed one.
exit_code: Option<i32>,
},
/// An existing controlled repository is not a valid bare mirror.
InvalidMirror,
/// A controlled mirror required by a read operation does not exist yet.
MirrorMissing,
/// A revision supplied to snapshot materialization is not a safe Git object identity.
InvalidRevision,
/// An existing snapshot does not match the requested revision or is not clean.
InvalidSnapshot,
/// The deterministic backend-owned write worktree path already exists unexpectedly.
WriteWorktreeExists,
/// A mutation would traverse or replace a symbolic link.
UnsafeMutationTarget,
/// A requested deletion target does not exist.
MutationTargetMissing,
/// A filesystem mutation or parent-directory preparation failed.
MutationFilesystemFailed,
/// The staged candidate paths do not exactly match the requested mutation set.
CandidateMutationMismatch,
/// The resulting candidate commit does not have the requested base as its sole first parent.
InvalidCandidate,
/// A backend-owned write worktree could not be removed deterministically.
WriteCleanupFailed,
/// Git returned output that does not satisfy the backend contract.
UnexpectedGitOutput {
/// Operation whose output could not be validated.
operation: crate::GitOperation,
},
}
impl std::fmt::Display for crate::GitBackendError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
crate::GitBackendError::EmptyGitExecutable => return formatter.write_str("git executable must not be empty"),
crate::GitBackendError::EmptyStateRoot => return formatter.write_str("git state root must not be empty"),
crate::GitBackendError::EmptySourceLocation => return formatter.write_str("git source location must not be empty"),
crate::GitBackendError::DuplicateSource => return formatter.write_str("git source identifier is already configured"),
crate::GitBackendError::SourceNotConfigured => return formatter.write_str("git source identifier is not configured"),
crate::GitBackendError::StatePreparationFailed => return formatter.write_str("git state directories could not be prepared"),
crate::GitBackendError::GitUnavailable { operation } => return write!(formatter, "git is unavailable while attempting to {operation}"),
crate::GitBackendError::GitCommandFailed { operation, exit_code } => {
return write!(formatter, "git failed while attempting to {operation}; exit code {exit_code:?}");
},
crate::GitBackendError::InvalidMirror => return formatter.write_str("controlled git repository is not a valid bare mirror"),
crate::GitBackendError::MirrorMissing => return formatter.write_str("controlled git mirror does not exist"),
crate::GitBackendError::InvalidRevision => return formatter.write_str("git revision is not a safe object identity"),
crate::GitBackendError::InvalidSnapshot => return formatter.write_str("controlled git snapshot is invalid or dirty"),
crate::GitBackendError::WriteWorktreeExists => return formatter.write_str("controlled git write worktree already exists"),
crate::GitBackendError::UnsafeMutationTarget => return formatter.write_str("git mutation target traverses a symbolic link"),
crate::GitBackendError::MutationTargetMissing => return formatter.write_str("git deletion target does not exist"),
crate::GitBackendError::MutationFilesystemFailed => return formatter.write_str("git candidate filesystem mutation failed"),
crate::GitBackendError::CandidateMutationMismatch => return formatter.write_str("git candidate staged paths do not match requested mutations"),
crate::GitBackendError::InvalidCandidate => return formatter.write_str("git candidate commit does not match the requested base"),
crate::GitBackendError::WriteCleanupFailed => return formatter.write_str("controlled git write worktree cleanup failed"),
crate::GitBackendError::UnexpectedGitOutput { operation } => {
return write!(formatter, "git returned unexpected output while attempting to {operation}");
},
}
}
}
impl std::error::Error for crate::GitBackendError {}

View File

@@ -0,0 +1,33 @@
// file: crates/knowledge-git-lib/src/lib.rs
// version: 5
//! Controlled Git process backend for Knowledge content sources.
//!
//! This crate owns physical Git source configuration, generated-state layout, provider-neutral read
//! materialization, isolated candidate construction, guarded remote branch updates and process execution.
//! It deliberately contains no forge API, persistence, scheduler or application orchestration.
mod backend;
mod error;
mod write;
/// Controlled Git backend that owns configured sources and local read materialization.
pub use self::backend::GitBackend;
/// Explicit physical configuration for one controlled Git backend instance.
pub use self::backend::GitBackendConfig;
/// Description of one initialized or already existing controlled mirror.
pub use self::backend::GitMirror;
/// Result of ensuring that a controlled mirror exists.
pub use self::backend::GitMirrorStatus;
/// Physical Git source configuration associated with one logical source identifier.
pub use self::backend::GitSourceConfig;
/// Path-safe storage key derived from a logical Knowledge source identifier.
pub use self::backend::GitSourceKey;
/// Coarse physical source kind used by the backend.
pub use self::backend::GitSourceKind;
/// Recoverable error returned by the controlled Git backend.
pub use self::error::GitBackendError;
/// Git operation category exposed by recoverable process errors without leaking command arguments.
pub use self::error::GitOperation;
pub(crate) use self::backend::is_safe_revision;

View File

@@ -0,0 +1,505 @@
// file: crates/knowledge-git-lib/src/write.rs
// version: 2
//! Isolated candidate-commit creation and guarded remote publication for controlled Git sources.
impl crate::GitBackend {
/// Create one detached candidate commit from an explicit resolved base revision.
///
/// This operation does not update any remote branch. The candidate is retained by an internal
/// backend reference so a later guarded push can publish it without depending on worktree
/// lifetime or reflog retention.
pub fn create_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitWriteRequest,
) -> Result<knowledge_git_api::GitRevision, crate::GitBackendError> {
if !crate::is_safe_revision(request.base_revision().as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
let source_result = self.configured_source(source_id);
let source = match source_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let preparation_result = self.prepare_state_directories();
match preparation_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let mirror_result = self.ensure_mirror(source);
let mirror = match mirror_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let source_key = crate::GitSourceKey::from_source_id(source_id);
let source_write_root = self.config().write_worktrees_path().join(source_key.as_str());
let worktree_path = source_write_root.join("candidate");
if worktree_path.exists() {
return Err(crate::GitBackendError::WriteWorktreeExists);
}
let create_parent_result = std::fs::create_dir_all(&source_write_root);
if create_parent_result.is_err() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
let add_result = self.run_controlled_git(
crate::GitOperation::CreateWriteWorktree,
vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("worktree"),
std::ffi::OsString::from("add"),
std::ffi::OsString::from("--detach"),
std::ffi::OsString::from("--"),
worktree_path.as_os_str().to_os_string(),
std::ffi::OsString::from(request.base_revision().as_str()),
],
Vec::new(),
);
match add_result {
Ok(_) => (),
Err(error) => return Err(error),
}
let candidate_result = self.populate_candidate_worktree(mirror.repository_path(), &worktree_path, request);
let cleanup_result = self.remove_candidate_worktree(mirror.repository_path(), &worktree_path, candidate_result.is_err());
match cleanup_result {
Ok(()) => return candidate_result,
Err(error) => return Err(error),
}
}
fn populate_candidate_worktree(
&self,
repository_path: &std::path::Path,
worktree_path: &std::path::Path,
request: &knowledge_git_api::GitWriteRequest,
) -> Result<knowledge_git_api::GitRevision, crate::GitBackendError> {
for mutation in request.mutations() {
let mutation_result = apply_mutation(worktree_path, mutation);
match mutation_result {
Ok(()) => (),
Err(error) => return Err(error),
}
}
let mut add_arguments = vec![
std::ffi::OsString::from("-C"),
worktree_path.as_os_str().to_os_string(),
std::ffi::OsString::from("add"),
std::ffi::OsString::from("--all"),
std::ffi::OsString::from("--"),
];
for mutation in request.mutations() {
add_arguments.push(std::ffi::OsString::from(mutation.path().as_str()));
}
let add_result = self.run_controlled_git(crate::GitOperation::StageCandidate, add_arguments, Vec::new());
match add_result {
Ok(_) => (),
Err(error) => return Err(error),
}
let staged_result = self.run_controlled_git(
crate::GitOperation::InspectCandidate,
vec![
std::ffi::OsString::from("-C"),
worktree_path.as_os_str().to_os_string(),
std::ffi::OsString::from("diff"),
std::ffi::OsString::from("--cached"),
std::ffi::OsString::from("--name-only"),
std::ffi::OsString::from("-z"),
std::ffi::OsString::from("--"),
],
Vec::new(),
);
let staged = match staged_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if !staged_paths_match(&staged, request.mutations()) {
return Err(crate::GitBackendError::CandidateMutationMismatch);
}
let identity = request.identity();
let environment = vec![
(std::ffi::OsString::from("GIT_AUTHOR_NAME"), std::ffi::OsString::from(identity.name())),
(std::ffi::OsString::from("GIT_AUTHOR_EMAIL"), std::ffi::OsString::from(identity.email())),
(std::ffi::OsString::from("GIT_COMMITTER_NAME"), std::ffi::OsString::from(identity.name())),
(std::ffi::OsString::from("GIT_COMMITTER_EMAIL"), std::ffi::OsString::from(identity.email())),
];
let commit_result = self.run_controlled_git(
crate::GitOperation::CommitCandidate,
vec![
std::ffi::OsString::from("-C"),
worktree_path.as_os_str().to_os_string(),
std::ffi::OsString::from("commit"),
std::ffi::OsString::from("--no-verify"),
std::ffi::OsString::from("--no-gpg-sign"),
std::ffi::OsString::from("--cleanup=verbatim"),
std::ffi::OsString::from("-m"),
std::ffi::OsString::from(request.message()),
],
environment,
);
match commit_result {
Ok(_) => (),
Err(error) => return Err(error),
}
let candidate_result = self.resolve_worktree_revision(worktree_path, "HEAD", crate::GitOperation::InspectCandidate);
let candidate = match candidate_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let parent_result = self.resolve_worktree_revision(worktree_path, "HEAD^1", crate::GitOperation::InspectCandidate);
let parent = match parent_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if &parent != request.base_revision() || candidate == parent {
return Err(crate::GitBackendError::InvalidCandidate);
}
let candidate_reference = format!("refs/knowledge/candidates/{}", candidate.as_str());
let preserve_result = self.run_controlled_git(
crate::GitOperation::PreserveCandidate,
vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("update-ref"),
std::ffi::OsString::from(candidate_reference),
std::ffi::OsString::from(candidate.as_str()),
],
Vec::new(),
);
match preserve_result {
Ok(_) => return Ok(candidate),
Err(error) => return Err(error),
}
}
fn resolve_worktree_revision(
&self,
worktree_path: &std::path::Path,
expression: &str,
operation: crate::GitOperation,
) -> Result<knowledge_git_api::GitRevision, crate::GitBackendError> {
let output_result = self.run_controlled_git(
operation,
vec![
std::ffi::OsString::from("-C"),
worktree_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from("--end-of-options"),
std::ffi::OsString::from(expression),
],
Vec::new(),
);
let output = match output_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let text_result = std::str::from_utf8(&output);
let text = match text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::UnexpectedGitOutput { operation }),
};
if !crate::is_safe_revision(text) {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation });
}
return Ok(knowledge_git_api::GitRevision::new(text));
}
fn remove_candidate_worktree(&self, repository_path: &std::path::Path, worktree_path: &std::path::Path, force: bool) -> Result<(), crate::GitBackendError> {
let mut arguments = vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("worktree"),
std::ffi::OsString::from("remove"),
];
if force {
arguments.push(std::ffi::OsString::from("--force"));
}
arguments.push(std::ffi::OsString::from("--"));
arguments.push(worktree_path.as_os_str().to_os_string());
let remove_result = self.run_controlled_git(crate::GitOperation::RemoveWriteWorktree, arguments, Vec::new());
match remove_result {
Ok(_) => return Ok(()),
Err(_) => return Err(crate::GitBackendError::WriteCleanupFailed),
}
}
/// Attempt one guarded remote branch update for a retained candidate revision.
///
/// The remote is first observed for an early conflict result, then Git's server-side
/// `--force-with-lease=<ref>:<expected>` guard performs the authoritative compare-and-exchange.
/// A failed guarded push is observed again so a concurrent update is returned as a recoverable
/// [`knowledge_git_api::GitPushOutcome::Conflict`] instead of being collapsed into a process error.
pub fn push_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitPushRequest,
) -> Result<knowledge_git_api::GitPushOutcome, crate::GitBackendError> {
if !crate::is_safe_revision(request.candidate_revision().as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
let source_result = self.configured_source(source_id);
let source = match source_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let mirror_result = self.ensure_mirror(source);
let mirror = match mirror_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let candidate_result = self.validate_retained_candidate(mirror.repository_path(), request.candidate_revision());
match candidate_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let observed_result = self.observe_remote_state(mirror.repository_path(), request.target_branch());
let observed = match observed_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if &observed != request.expected_remote() {
return Ok(knowledge_git_api::GitPushOutcome::Conflict { actual: observed });
}
let lease = match request.expected_remote() {
knowledge_git_api::GitRemoteState::Absent => format!("--force-with-lease={}:", request.target_branch().as_str()),
knowledge_git_api::GitRemoteState::Revision(revision) => {
if !crate::is_safe_revision(revision.as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
format!("--force-with-lease={}:{}", request.target_branch().as_str(), revision.as_str())
},
};
let refspec = format!("{}:{}", request.candidate_revision().as_str(), request.target_branch().as_str());
let push_result = self.run_controlled_git(
crate::GitOperation::PushCandidate,
vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("-c"),
std::ffi::OsString::from("remote.origin.mirror=false"),
std::ffi::OsString::from("push"),
std::ffi::OsString::from("--porcelain"),
std::ffi::OsString::from("--no-verify"),
std::ffi::OsString::from(lease),
std::ffi::OsString::from("origin"),
std::ffi::OsString::from(refspec),
],
Vec::new(),
);
match push_result {
Ok(_) => return Ok(knowledge_git_api::GitPushOutcome::Updated(request.candidate_revision().clone())),
Err(push_error) => {
let after_result = self.observe_remote_state(mirror.repository_path(), request.target_branch());
let after = match after_result {
Ok(value) => value,
Err(_) => return Err(push_error),
};
if &after != request.expected_remote() {
if after == knowledge_git_api::GitRemoteState::Revision(request.candidate_revision().clone()) {
return Ok(knowledge_git_api::GitPushOutcome::Updated(request.candidate_revision().clone()));
}
return Ok(knowledge_git_api::GitPushOutcome::Conflict { actual: after });
}
return Err(push_error);
},
}
}
fn validate_retained_candidate(&self, repository_path: &std::path::Path, candidate: &knowledge_git_api::GitRevision) -> Result<(), crate::GitBackendError> {
let candidate_reference = format!("refs/knowledge/candidates/{}^{{commit}}", candidate.as_str());
let output_result = self.run_controlled_git(
crate::GitOperation::InspectCandidate,
vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from("--end-of-options"),
std::ffi::OsString::from(candidate_reference),
],
Vec::new(),
);
let output = match output_result {
Ok(value) => value,
Err(_) => return Err(crate::GitBackendError::InvalidCandidate),
};
let text_result = std::str::from_utf8(&output);
let text = match text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::InvalidCandidate),
};
if text != candidate.as_str() {
return Err(crate::GitBackendError::InvalidCandidate);
}
return Ok(());
}
fn observe_remote_state(
&self,
repository_path: &std::path::Path,
branch: &knowledge_git_api::GitBranch,
) -> Result<knowledge_git_api::GitRemoteState, crate::GitBackendError> {
let output_result = self.run_controlled_git(
crate::GitOperation::InspectRemote,
vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("ls-remote"),
std::ffi::OsString::from("--refs"),
std::ffi::OsString::from("origin"),
std::ffi::OsString::from(branch.as_str()),
],
Vec::new(),
);
let output = match output_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if output.is_empty() {
return Ok(knowledge_git_api::GitRemoteState::Absent);
}
let text_result = std::str::from_utf8(&output);
let text = match text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
let mut lines = text.lines();
let line = match lines.next() {
Some(value) => value,
None => return Ok(knowledge_git_api::GitRemoteState::Absent),
};
if lines.next().is_some() {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote });
}
let mut fields = line.split('\t');
let revision = match fields.next() {
Some(value) => value,
None => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
let remote_reference = match fields.next() {
Some(value) => value,
None => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
if fields.next().is_some() || remote_reference != branch.as_str() || !crate::is_safe_revision(revision) {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote });
}
return Ok(knowledge_git_api::GitRemoteState::Revision(knowledge_git_api::GitRevision::new(revision)));
}
}
impl knowledge_git_api::GitRepositoryWriter for crate::GitBackend {
type Error = crate::GitBackendError;
fn create_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitWriteRequest,
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
return crate::GitBackend::create_candidate(self, source_id, request);
}
fn push_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitPushRequest,
) -> Result<knowledge_git_api::GitPushOutcome, Self::Error> {
return crate::GitBackend::push_candidate(self, source_id, request);
}
}
fn apply_mutation(worktree_path: &std::path::Path, mutation: &knowledge_git_api::GitFileMutation) -> Result<(), crate::GitBackendError> {
let target_result = checked_target_path(worktree_path, mutation.path());
let target = match target_result {
Ok(value) => value,
Err(error) => return Err(error),
};
match mutation {
knowledge_git_api::GitFileMutation::Write { content, .. } => {
let parent = match target.parent() {
Some(value) => value,
None => return Err(crate::GitBackendError::MutationFilesystemFailed),
};
let create_result = std::fs::create_dir_all(parent);
if create_result.is_err() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
let write_result = std::fs::write(target, content);
if write_result.is_err() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
return Ok(());
},
knowledge_git_api::GitFileMutation::Delete { .. } => {
if !target.exists() {
return Err(crate::GitBackendError::MutationTargetMissing);
}
let remove_result = std::fs::remove_file(target);
if remove_result.is_err() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
return Ok(());
},
}
}
fn checked_target_path(
worktree_path: &std::path::Path,
mutation_path: &knowledge_git_api::GitMutationPath,
) -> Result<std::path::PathBuf, crate::GitBackendError> {
let components: Vec<&str> = mutation_path.as_str().split('/').collect();
let mut current = worktree_path.to_path_buf();
for (index, component) in components.iter().enumerate() {
current.push(*component);
let metadata_result = std::fs::symlink_metadata(&current);
let metadata = match metadata_result {
Ok(value) => Some(value),
Err(error) => {
if error.kind() == std::io::ErrorKind::NotFound {
None
} else {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
},
};
let metadata = match metadata {
Some(value) => value,
None => continue,
};
if metadata.file_type().is_symlink() {
return Err(crate::GitBackendError::UnsafeMutationTarget);
}
let is_final = index + 1 == components.len();
if !is_final && !metadata.is_dir() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
if is_final && metadata.is_dir() {
return Err(crate::GitBackendError::MutationFilesystemFailed);
}
}
return Ok(current);
}
fn staged_paths_match(staged: &[u8], mutations: &[knowledge_git_api::GitFileMutation]) -> bool {
let mut observed = std::collections::HashSet::new();
for bytes in staged.split(|byte| return *byte == 0) {
if bytes.is_empty() {
continue;
}
let path_result = std::str::from_utf8(bytes);
let path = match path_result {
Ok(value) => value,
Err(_) => return false,
};
observed.insert(path);
}
if observed.len() != mutations.len() {
return false;
}
for mutation in mutations {
if !observed.contains(mutation.path().as_str()) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,200 @@
// file: crates/knowledge-git-lib/tests/git_read_path.rs
// version: 2
//! Integration tests for the real generic Git read path using local temporary repositories.
fn test_root(label: &str) -> std::path::PathBuf {
let nanos_result = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
let nanos = match nanos_result {
Ok(value) => value.as_nanos(),
Err(_) => 0,
};
let mut path = std::env::temp_dir();
path.push(format!("sasedev-knowledge-git-integration-{label}-{}-{nanos}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
return path;
}
fn run_git(repository: &std::path::Path, arguments: &[&str]) -> bool {
let mut command = std::process::Command::new("git");
command.current_dir(repository);
command.args(arguments);
command.env("GIT_TERMINAL_PROMPT", "0");
let status_result = command.status();
match status_result {
Ok(status) => return status.success(),
Err(_) => return false,
}
}
fn prepare_source(root: &std::path::Path) -> Option<std::path::PathBuf> {
let source = root.join("source");
if std::fs::create_dir_all(&source).is_err() {
return None;
}
if !run_git(&source, &["init", "-b", "main"]) {
return None;
}
if !run_git(&source, &["config", "user.name", "Knowledge Test"]) {
return None;
}
if !run_git(&source, &["config", "user.email", "knowledge@example.invalid"]) {
return None;
}
if !run_git(&source, &["config", "commit.gpgsign", "false"]) {
return None;
}
if std::fs::write(source.join("content.txt"), "first\n").is_err() {
return None;
}
if !run_git(&source, &["add", "content.txt"]) {
return None;
}
if !run_git(&source, &["commit", "-m", "first"]) {
return None;
}
return Some(source);
}
fn source_id() -> Option<knowledge_domain_lib::SourceId> {
let result = knowledge_domain_lib::SourceId::new("integration-source");
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn configured_backend(root: &std::path::Path, source: &std::path::Path) -> Option<(knowledge_git_lib::GitBackend, knowledge_domain_lib::SourceId)> {
let source_id_result = source_id();
let source_id = match source_id_result {
Some(value) => value,
None => return None,
};
let config_result = knowledge_git_lib::GitBackendConfig::new(root.join("state"));
let config = match config_result {
Ok(value) => value,
Err(_) => return None,
};
let source_result = knowledge_git_lib::GitSourceConfig::local(source_id.clone(), source.to_path_buf());
let source_config = match source_result {
Ok(value) => value,
Err(_) => return None,
};
let mut backend = knowledge_git_lib::GitBackend::new(config);
if backend.register_source(source_config).is_err() {
return None;
}
return Some((backend, source_id));
}
#[test]
fn real_local_repository_refreshes_resolves_and_materializes_revision_snapshots() {
let root = test_root("refresh-resolve-snapshot");
let source_result = prepare_source(&root);
assert!(source_result.is_some());
let source = match source_result {
Some(value) => value,
None => return,
};
let backend_result = configured_backend(&root, &source);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
assert!(knowledge_git_api::GitRepository::refresh(&mut backend, &source_id).is_ok());
let first_revision_result = knowledge_git_api::GitRepository::resolve_revision(&backend, &source_id, &reference);
assert!(first_revision_result.is_ok());
let first_revision = match first_revision_result {
Ok(value) => value,
Err(_) => return,
};
let first_snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &first_revision);
assert!(first_snapshot_result.is_ok());
let first_snapshot = match first_snapshot_result {
Ok(value) => value,
Err(_) => return,
};
assert!(first_snapshot.path().is_dir());
let first_content_result = std::fs::read_to_string(first_snapshot.path().join("content.txt"));
assert!(first_content_result.is_ok());
let first_content = match first_content_result {
Ok(value) => value,
Err(_) => return,
};
assert_eq!(first_content, "first\n");
let repeated_snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &first_revision);
assert!(repeated_snapshot_result.is_ok());
let repeated_snapshot = match repeated_snapshot_result {
Ok(value) => value,
Err(_) => return,
};
assert_eq!(first_snapshot.path(), repeated_snapshot.path());
assert!(std::fs::write(source.join("content.txt"), "second\n").is_ok());
assert!(run_git(&source, &["add", "content.txt"]));
assert!(run_git(&source, &["commit", "-m", "second"]));
assert!(knowledge_git_api::GitRepository::refresh(&mut backend, &source_id).is_ok());
let second_revision_result = knowledge_git_api::GitRepository::resolve_revision(&backend, &source_id, &reference);
assert!(second_revision_result.is_ok());
let second_revision = match second_revision_result {
Ok(value) => value,
Err(_) => return,
};
assert_ne!(first_revision, second_revision);
let second_snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &second_revision);
assert!(second_snapshot_result.is_ok());
let second_snapshot = match second_snapshot_result {
Ok(value) => value,
Err(_) => return,
};
assert_ne!(first_snapshot.path(), second_snapshot.path());
let second_content_result = std::fs::read_to_string(second_snapshot.path().join("content.txt"));
assert!(second_content_result.is_ok());
let second_content = match second_content_result {
Ok(value) => value,
Err(_) => return,
};
assert_eq!(second_content, "second\n");
let _ = std::fs::remove_dir_all(&root);
return;
}
#[test]
fn invalid_reference_and_dirty_existing_snapshot_are_rejected() {
let root = test_root("invalid-and-dirty");
let source_result = prepare_source(&root);
assert!(source_result.is_some());
let source = match source_result {
Some(value) => value,
None => return,
};
let backend_result = configured_backend(&root, &source);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
assert!(knowledge_git_api::GitRepository::refresh(&mut backend, &source_id).is_ok());
let missing_reference = knowledge_git_api::GitReference::new("refs/heads/does-not-exist");
let missing_result = knowledge_git_api::GitRepository::resolve_revision(&backend, &source_id, &missing_reference);
assert!(missing_result.is_err());
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let revision_result = knowledge_git_api::GitRepository::resolve_revision(&backend, &source_id, &reference);
assert!(revision_result.is_ok());
let revision = match revision_result {
Ok(value) => value,
Err(_) => return,
};
let snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &revision);
assert!(snapshot_result.is_ok());
let snapshot = match snapshot_result {
Ok(value) => value,
Err(_) => return,
};
assert!(std::fs::write(snapshot.path().join("content.txt"), "dirty\n").is_ok());
let repeated_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &revision);
assert_eq!(repeated_result, Err(knowledge_git_lib::GitBackendError::InvalidSnapshot));
let _ = std::fs::remove_dir_all(&root);
return;
}

View File

@@ -0,0 +1,312 @@
// file: crates/knowledge-git-lib/tests/git_write_candidate.rs
// version: 2
//! Integration tests for isolated real Git candidate creation using local temporary repositories.
fn test_root(label: &str) -> std::path::PathBuf {
let nanos_result = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
let nanos = match nanos_result {
Ok(value) => value.as_nanos(),
Err(_) => 0,
};
let mut path = std::env::temp_dir();
path.push(format!("sasedev-knowledge-git-write-{label}-{}-{nanos}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
return path;
}
fn run_git(repository: &std::path::Path, arguments: &[&str]) -> bool {
let mut command = std::process::Command::new("git");
command.current_dir(repository);
command.args(arguments);
command.env("GIT_TERMINAL_PROMPT", "0");
let status_result = command.status();
match status_result {
Ok(status) => return status.success(),
Err(_) => return false,
}
}
fn run_git_output(arguments: Vec<std::ffi::OsString>) -> Option<Vec<u8>> {
let mut command = std::process::Command::new("git");
command.args(arguments);
command.env("GIT_TERMINAL_PROMPT", "0");
let output_result = command.output();
let output = match output_result {
Ok(value) => value,
Err(_) => return None,
};
if !output.status.success() {
return None;
}
return Some(output.stdout);
}
fn prepare_source(root: &std::path::Path) -> Option<std::path::PathBuf> {
let source = root.join("source");
if std::fs::create_dir_all(&source).is_err() {
return None;
}
if !run_git(&source, &["init", "-b", "main"]) {
return None;
}
if !run_git(&source, &["config", "user.name", "Ambient Identity"]) {
return None;
}
if !run_git(&source, &["config", "user.email", "ambient@example.invalid"]) {
return None;
}
if !run_git(&source, &["config", "commit.gpgsign", "false"]) {
return None;
}
if std::fs::write(source.join("content.txt"), "first\n").is_err() {
return None;
}
if std::fs::write(source.join("delete.txt"), "delete me\n").is_err() {
return None;
}
if !run_git(&source, &["add", "content.txt", "delete.txt"]) {
return None;
}
if !run_git(&source, &["commit", "-m", "first"]) {
return None;
}
return Some(source);
}
fn configured_backend(root: &std::path::Path, source: &std::path::Path) -> Option<(knowledge_git_lib::GitBackend, knowledge_domain_lib::SourceId)> {
let source_id_result = knowledge_domain_lib::SourceId::new("integration-write-source");
let source_id = match source_id_result {
Ok(value) => value,
Err(_) => return None,
};
let config_result = knowledge_git_lib::GitBackendConfig::new(root.join("state"));
let config = match config_result {
Ok(value) => value,
Err(_) => return None,
};
let source_result = knowledge_git_lib::GitSourceConfig::local(source_id.clone(), source.to_path_buf());
let source_config = match source_result {
Ok(value) => value,
Err(_) => return None,
};
let mut backend = knowledge_git_lib::GitBackend::new(config);
if backend.register_source(source_config).is_err() {
return None;
}
return Some((backend, source_id));
}
fn resolved_main(backend: &mut knowledge_git_lib::GitBackend, source_id: &knowledge_domain_lib::SourceId) -> Option<knowledge_git_api::GitRevision> {
if knowledge_git_api::GitRepository::refresh(backend, source_id).is_err() {
return None;
}
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let revision_result = knowledge_git_api::GitRepository::resolve_revision(backend, source_id, &reference);
match revision_result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn mutation_path(value: &str) -> Option<knowledge_git_api::GitMutationPath> {
let result = knowledge_git_api::GitMutationPath::new(value);
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn commit_identity() -> Option<knowledge_git_api::GitCommitIdentity> {
let result = knowledge_git_api::GitCommitIdentity::new("Knowledge Writer", "writer@example.invalid");
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
#[test]
fn candidate_commit_applies_exact_mutations_uses_explicit_identity_and_cleans_worktree() {
let root = test_root("candidate");
let source_result = prepare_source(&root);
assert!(source_result.is_some());
let source = match source_result {
Some(value) => value,
None => return,
};
let backend_result = configured_backend(&root, &source);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
let base_result = resolved_main(&mut backend, &source_id);
assert!(base_result.is_some());
let base = match base_result {
Some(value) => value,
None => return,
};
let snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(&backend, &source_id, &base);
assert!(snapshot_result.is_ok());
let snapshot = match snapshot_result {
Ok(value) => value,
Err(_) => return,
};
let content_path = match mutation_path("content.txt") {
Some(value) => value,
None => return,
};
let nested_path = match mutation_path("nested/new.txt") {
Some(value) => value,
None => return,
};
let delete_path = match mutation_path("delete.txt") {
Some(value) => value,
None => return,
};
let identity = match commit_identity() {
Some(value) => value,
None => return,
};
let request_result = knowledge_git_api::GitWriteRequest::new(
base.clone(),
vec![
knowledge_git_api::GitFileMutation::Write { path: content_path, content: b"second\n".to_vec() },
knowledge_git_api::GitFileMutation::Write { path: nested_path, content: b"nested\n".to_vec() },
knowledge_git_api::GitFileMutation::Delete { path: delete_path },
],
identity,
"candidate commit",
);
assert!(request_result.is_ok());
let request = match request_result {
Ok(value) => value,
Err(_) => return,
};
let candidate_result = backend.create_candidate(&source_id, &request);
assert!(candidate_result.is_ok());
let candidate = match candidate_result {
Ok(value) => value,
Err(_) => return,
};
assert_ne!(candidate, base);
let repository_path = backend.repository_path(&source_id);
let parent_output = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from(format!("{}^1", candidate.as_str())),
]);
assert!(parent_output.is_some());
let parent = match parent_output {
Some(value) => String::from_utf8_lossy(&value).trim().to_owned(),
None => return,
};
assert_eq!(parent, base.as_str());
let content_output = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("show"),
std::ffi::OsString::from(format!("{}:content.txt", candidate.as_str())),
]);
assert_eq!(content_output, Some(b"second\n".to_vec()));
let nested_output = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("show"),
std::ffi::OsString::from(format!("{}:nested/new.txt", candidate.as_str())),
]);
assert_eq!(nested_output, Some(b"nested\n".to_vec()));
let deleted_output = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("show"),
std::ffi::OsString::from(format!("{}:delete.txt", candidate.as_str())),
]);
assert!(deleted_output.is_none());
let identity_output = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("show"),
std::ffi::OsString::from("-s"),
std::ffi::OsString::from("--format=%an%x00%ae%x00%cn%x00%ce"),
std::ffi::OsString::from(candidate.as_str()),
]);
assert!(identity_output.is_some());
let identity_text = match identity_output {
Some(value) => String::from_utf8_lossy(&value).trim().to_owned(),
None => return,
};
assert_eq!(identity_text, "Knowledge Writer\0writer@example.invalid\0Knowledge Writer\0writer@example.invalid");
let candidate_ref = format!("refs/knowledge/candidates/{}", candidate.as_str());
let retained = run_git_output(vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("show-ref"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from(candidate_ref),
]);
assert!(retained.is_some());
let source_key = knowledge_git_lib::GitSourceKey::from_source_id(&source_id);
assert!(!backend.config().write_worktrees_path().join(source_key.as_str()).join("candidate").exists());
let snapshot_content = std::fs::read_to_string(snapshot.path().join("content.txt"));
assert!(matches!(snapshot_content.as_deref(), Ok("first\n")));
assert!(snapshot.path().join("delete.txt").is_file());
let _ = std::fs::remove_dir_all(&root);
return;
}
#[cfg(unix)]
#[test]
fn candidate_rejects_symlink_escape_and_removes_owned_failed_worktree() {
let root = test_root("symlink");
let source_result = prepare_source(&root);
assert!(source_result.is_some());
let source = match source_result {
Some(value) => value,
None => return,
};
let outside = root.join("outside");
assert!(std::fs::create_dir_all(&outside).is_ok());
assert!(std::os::unix::fs::symlink(&outside, source.join("escape")).is_ok());
assert!(run_git(&source, &["add", "escape"]));
assert!(run_git(&source, &["commit", "-m", "add escape symlink"]));
let backend_result = configured_backend(&root, &source);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
let base_result = resolved_main(&mut backend, &source_id);
assert!(base_result.is_some());
let base = match base_result {
Some(value) => value,
None => return,
};
let path = match mutation_path("escape/pwned.txt") {
Some(value) => value,
None => return,
};
let identity = match commit_identity() {
Some(value) => value,
None => return,
};
let request_result = knowledge_git_api::GitWriteRequest::new(
base,
vec![knowledge_git_api::GitFileMutation::Write { path, content: b"forbidden\n".to_vec() }],
identity,
"must fail",
);
assert!(request_result.is_ok());
let request = match request_result {
Ok(value) => value,
Err(_) => return,
};
let result = backend.create_candidate(&source_id, &request);
assert_eq!(result, Err(knowledge_git_lib::GitBackendError::UnsafeMutationTarget));
assert!(!outside.join("pwned.txt").exists());
let source_key = knowledge_git_lib::GitSourceKey::from_source_id(&source_id);
assert!(!backend.config().write_worktrees_path().join(source_key.as_str()).join("candidate").exists());
let _ = std::fs::remove_dir_all(&root);
return;
}

View File

@@ -0,0 +1,282 @@
// file: crates/knowledge-git-lib/tests/git_write_push.rs
// version: 1
//! Integration tests for guarded real Git pushes using local bare remotes.
fn test_root(label: &str) -> std::path::PathBuf {
let nanos_result = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
let nanos = match nanos_result {
Ok(value) => value.as_nanos(),
Err(_) => 0,
};
let mut path = std::env::temp_dir();
path.push(format!("sasedev-knowledge-git-push-{label}-{}-{nanos}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
return path;
}
fn run_git(repository: &std::path::Path, arguments: &[&str]) -> bool {
let mut command = std::process::Command::new("git");
command.current_dir(repository);
command.args(arguments);
command.env("GIT_TERMINAL_PROMPT", "0");
let status_result = command.status();
match status_result {
Ok(status) => return status.success(),
Err(_) => return false,
}
}
fn run_git_with_path(repository: &std::path::Path, arguments: &[&str], path: &std::path::Path) -> bool {
let mut command = std::process::Command::new("git");
command.current_dir(repository);
command.args(arguments);
command.arg(path);
command.env("GIT_TERMINAL_PROMPT", "0");
let status_result = command.status();
match status_result {
Ok(status) => return status.success(),
Err(_) => return false,
}
}
fn run_git_dir_output(repository: &std::path::Path, reference: &str) -> Option<String> {
let mut command = std::process::Command::new("git");
command.arg("--git-dir");
command.arg(repository);
command.arg("rev-parse");
command.arg("--verify");
command.arg(reference);
command.env("GIT_TERMINAL_PROMPT", "0");
let output_result = command.output();
let output = match output_result {
Ok(value) => value,
Err(_) => return None,
};
if !output.status.success() {
return None;
}
let text_result = std::str::from_utf8(&output.stdout);
match text_result {
Ok(value) => return Some(value.trim().to_owned()),
Err(_) => return None,
}
}
fn prepare_remote(root: &std::path::Path) -> Option<(std::path::PathBuf, std::path::PathBuf)> {
let remote = root.join("remote.git");
let seed = root.join("seed");
if std::fs::create_dir_all(root).is_err() {
return None;
}
if !run_git_with_path(root, &["init", "--bare"], &remote) {
return None;
}
if !run_git_with_path(root, &["init", "-b", "main"], &seed) {
return None;
}
if !run_git(&seed, &["config", "user.name", "Remote Writer"]) {
return None;
}
if !run_git(&seed, &["config", "user.email", "remote@example.invalid"]) {
return None;
}
if !run_git(&seed, &["config", "commit.gpgsign", "false"]) {
return None;
}
if std::fs::write(seed.join("content.txt"), "first\n").is_err() {
return None;
}
if !run_git(&seed, &["add", "content.txt"]) {
return None;
}
if !run_git(&seed, &["commit", "-m", "first"]) {
return None;
}
if !run_git_with_path(&seed, &["remote", "add", "origin"], &remote) {
return None;
}
if !run_git(&seed, &["push", "origin", "main"]) {
return None;
}
return Some((remote, seed));
}
fn configured_backend(root: &std::path::Path, remote: &std::path::Path) -> Option<(knowledge_git_lib::GitBackend, knowledge_domain_lib::SourceId)> {
let source_id_result = knowledge_domain_lib::SourceId::new("integration-push-source");
let source_id = match source_id_result {
Ok(value) => value,
Err(_) => return None,
};
let config_result = knowledge_git_lib::GitBackendConfig::new(root.join("state"));
let config = match config_result {
Ok(value) => value,
Err(_) => return None,
};
let source_result = knowledge_git_lib::GitSourceConfig::local(source_id.clone(), remote.to_path_buf());
let source_config = match source_result {
Ok(value) => value,
Err(_) => return None,
};
let mut backend = knowledge_git_lib::GitBackend::new(config);
if backend.register_source(source_config).is_err() {
return None;
}
return Some((backend, source_id));
}
fn resolved_main(backend: &mut knowledge_git_lib::GitBackend, source_id: &knowledge_domain_lib::SourceId) -> Option<knowledge_git_api::GitRevision> {
if knowledge_git_api::GitRepository::refresh(backend, source_id).is_err() {
return None;
}
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let revision_result = knowledge_git_api::GitRepository::resolve_revision(backend, source_id, &reference);
match revision_result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn mutation_path(value: &str) -> Option<knowledge_git_api::GitMutationPath> {
let result = knowledge_git_api::GitMutationPath::new(value);
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn commit_identity() -> Option<knowledge_git_api::GitCommitIdentity> {
let result = knowledge_git_api::GitCommitIdentity::new("Knowledge Writer", "writer@example.invalid");
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn branch(value: &str) -> Option<knowledge_git_api::GitBranch> {
let result = knowledge_git_api::GitBranch::new(value);
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn candidate_for(
backend: &mut knowledge_git_lib::GitBackend,
source_id: &knowledge_domain_lib::SourceId,
base: knowledge_git_api::GitRevision,
path: &str,
content: &[u8],
message: &str,
) -> Option<knowledge_git_api::GitRevision> {
let mutation = match mutation_path(path) {
Some(value) => knowledge_git_api::GitFileMutation::Write { path: value, content: content.to_vec() },
None => return None,
};
let identity = match commit_identity() {
Some(value) => value,
None => return None,
};
let request_result = knowledge_git_api::GitWriteRequest::new(base, vec![mutation], identity, message);
let request = match request_result {
Ok(value) => value,
Err(_) => return None,
};
let candidate_result = knowledge_git_api::GitRepositoryWriter::create_candidate(backend, source_id, &request);
match candidate_result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
#[test]
fn guarded_push_updates_expected_branch_and_creates_only_expected_absent_branch() {
let root = test_root("success");
let remote_result = prepare_remote(&root);
assert!(remote_result.is_some());
let (remote, _) = match remote_result {
Some(value) => value,
None => return,
};
let backend_result = configured_backend(&root, &remote);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
let base = match resolved_main(&mut backend, &source_id) {
Some(value) => value,
None => return,
};
let candidate = match candidate_for(&mut backend, &source_id, base.clone(), "content.txt", b"second\n", "second") {
Some(value) => value,
None => return,
};
let main = match branch("refs/heads/main") {
Some(value) => value,
None => return,
};
let push = knowledge_git_api::GitPushRequest::new(main, knowledge_git_api::GitRemoteState::Revision(base), candidate.clone());
let outcome = knowledge_git_api::GitRepositoryWriter::push_candidate(&mut backend, &source_id, &push);
assert_eq!(outcome, Ok(knowledge_git_api::GitPushOutcome::Updated(candidate.clone())));
assert_eq!(run_git_dir_output(&remote, "refs/heads/main"), Some(candidate.as_str().to_owned()));
let proposal_candidate = match candidate_for(&mut backend, &source_id, candidate, "proposal.txt", b"proposal\n", "proposal") {
Some(value) => value,
None => return,
};
let proposal = match branch("refs/heads/proposal") {
Some(value) => value,
None => return,
};
let create = knowledge_git_api::GitPushRequest::new(proposal, knowledge_git_api::GitRemoteState::Absent, proposal_candidate.clone());
let create_outcome = knowledge_git_api::GitRepositoryWriter::push_candidate(&mut backend, &source_id, &create);
assert_eq!(create_outcome, Ok(knowledge_git_api::GitPushOutcome::Updated(proposal_candidate.clone())));
assert_eq!(run_git_dir_output(&remote, "refs/heads/proposal"), Some(proposal_candidate.as_str().to_owned()));
let _ = std::fs::remove_dir_all(&root);
return;
}
#[test]
fn guarded_push_returns_conflict_after_concurrent_remote_change_without_overwrite() {
let root = test_root("conflict");
let remote_result = prepare_remote(&root);
assert!(remote_result.is_some());
let (remote, seed) = match remote_result {
Some(value) => value,
None => return,
};
let backend_result = configured_backend(&root, &remote);
assert!(backend_result.is_some());
let (mut backend, source_id) = match backend_result {
Some(value) => value,
None => return,
};
let base = match resolved_main(&mut backend, &source_id) {
Some(value) => value,
None => return,
};
let candidate = match candidate_for(&mut backend, &source_id, base.clone(), "content.txt", b"candidate\n", "candidate") {
Some(value) => value,
None => return,
};
assert!(std::fs::write(seed.join("content.txt"), "concurrent\n").is_ok());
assert!(run_git(&seed, &["add", "content.txt"]));
assert!(run_git(&seed, &["commit", "-m", "concurrent"]));
assert!(run_git(&seed, &["push", "origin", "main"]));
let concurrent_text = match run_git_dir_output(&remote, "refs/heads/main") {
Some(value) => value,
None => return,
};
let concurrent = knowledge_git_api::GitRevision::new(concurrent_text.as_str());
assert_ne!(concurrent, base);
let main = match branch("refs/heads/main") {
Some(value) => value,
None => return,
};
let request = knowledge_git_api::GitPushRequest::new(main, knowledge_git_api::GitRemoteState::Revision(base), candidate);
let outcome = knowledge_git_api::GitRepositoryWriter::push_candidate(&mut backend, &source_id, &request);
assert_eq!(outcome, Ok(knowledge_git_api::GitPushOutcome::Conflict { actual: knowledge_git_api::GitRemoteState::Revision(concurrent.clone()) }));
assert_eq!(run_git_dir_output(&remote, "refs/heads/main"), Some(concurrent.as_str().to_owned()));
let _ = std::fs::remove_dir_all(&root);
return;
}

View File

@@ -0,0 +1,303 @@
// file: crates/knowledge-git-lib/unit_tests/backend.rs
// version: 5
#[derive(Clone)]
struct RecordingRunner {
calls: std::rc::Rc<std::cell::RefCell<Vec<super::GitCommandSpec>>>,
failure: Option<crate::GitBackendError>,
inspection_stdout: Vec<u8>,
}
impl RecordingRunner {
fn successful() -> RecordingRunner {
return RecordingRunner {
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
failure: None,
inspection_stdout: b"true\n".to_vec(),
};
}
fn failing(error: crate::GitBackendError) -> RecordingRunner {
return RecordingRunner {
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
failure: Some(error),
inspection_stdout: b"true\n".to_vec(),
};
}
fn with_inspection_stdout(value: &[u8]) -> RecordingRunner {
return RecordingRunner {
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
failure: None,
inspection_stdout: value.to_vec(),
};
}
}
impl super::GitCommandRunner for RecordingRunner {
fn run(&self, specification: &super::GitCommandSpec) -> Result<super::GitCommandOutput, crate::GitBackendError> {
self.calls.borrow_mut().push(specification.clone());
if let Some(error) = self.failure {
return Err(error);
}
if specification.operation == crate::GitOperation::InspectMirror {
return Ok(super::GitCommandOutput { stdout: self.inspection_stdout.clone() });
}
return Ok(super::GitCommandOutput { stdout: Vec::new() });
}
}
fn source_id(value: &str) -> Option<knowledge_domain_lib::SourceId> {
let result = knowledge_domain_lib::SourceId::new(value);
match result {
Ok(source_id) => return Some(source_id),
Err(_) => return None,
}
}
fn test_root(label: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("sasedev-knowledge-git-lib-{label}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
return path;
}
#[test]
fn backend_configuration_requires_non_empty_paths() {
assert_eq!(crate::GitBackendConfig::new(std::path::PathBuf::new()), Err(crate::GitBackendError::EmptyStateRoot));
assert_eq!(
crate::GitBackendConfig::with_git_executable(test_root("empty-executable"), std::path::PathBuf::new()),
Err(crate::GitBackendError::EmptyGitExecutable)
);
return;
}
#[test]
fn duplicate_source_registration_is_rejected() {
let root = test_root("duplicate-source");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let source_id_result = source_id("pgr");
assert!(source_id_result.is_some());
let source_id = match source_id_result {
Some(value) => value,
None => return,
};
let source_result = crate::GitSourceConfig::local(source_id, std::path::PathBuf::from("/controlled/source"));
assert!(source_result.is_ok());
let source = match source_result {
Ok(value) => value,
Err(_) => return,
};
let mut backend = crate::GitBackend::new(config);
assert_eq!(backend.register_source(source.clone()), Ok(()));
assert_eq!(backend.register_source(source), Err(crate::GitBackendError::DuplicateSource));
let _ = std::fs::remove_dir_all(root);
return;
}
#[test]
fn source_key_never_interpolates_raw_source_identifier_as_a_path() {
let source_result = source_id("../pgr/credential:value");
assert!(source_result.is_some());
let source_id = match source_result {
Some(value) => value,
None => return,
};
let key = crate::GitSourceKey::from_source_id(&source_id);
assert!(key.as_str().starts_with("source-"));
assert!(key.as_str().chars().all(|character| return character.is_ascii_alphanumeric() || character == '-'));
assert!(!key.as_str().contains("pgr"));
assert!(!key.as_str().contains(".."));
return;
}
#[test]
fn local_source_clone_uses_mirror_no_local_and_argument_separator() {
let root = test_root("local-arguments");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let runner = RecordingRunner::successful();
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
let source_id_result = source_id("pgr");
assert!(source_id_result.is_some());
let source_id = match source_id_result {
Some(value) => value,
None => return,
};
let source_result = crate::GitSourceConfig::local(source_id, std::path::PathBuf::from("/controlled/source"));
assert!(source_result.is_ok());
let source = match source_result {
Ok(value) => value,
Err(_) => return,
};
let mirror_result = backend.ensure_mirror(&source);
assert!(mirror_result.is_ok());
let calls = runner.calls.borrow();
assert_eq!(calls.len(), 2);
assert_eq!(calls[0].operation, crate::GitOperation::CloneMirror);
assert_eq!(
calls[0].arguments,
vec![
std::ffi::OsString::from("clone"),
std::ffi::OsString::from("--mirror"),
std::ffi::OsString::from("--no-local"),
std::ffi::OsString::from("--"),
std::ffi::OsString::from("/controlled/source"),
backend.repository_path(source.source_id()).into_os_string(),
]
);
assert_eq!(calls[1].operation, crate::GitOperation::InspectMirror);
assert!(backend.config().hooks_path().is_dir());
assert!(backend.config().repositories_path().is_dir());
assert!(backend.config().snapshots_path().is_dir());
assert!(backend.config().write_worktrees_path().is_dir());
assert!(backend.config().empty_global_config_path().is_file());
assert_eq!(calls[0].global_config_path, backend.config().empty_global_config_path());
let _ = std::fs::remove_dir_all(root);
return;
}
#[test]
fn remote_source_clone_does_not_add_no_local() {
let root = test_root("remote-arguments");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let runner = RecordingRunner::successful();
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
let source_id_result = source_id("howtos");
assert!(source_id_result.is_some());
let source_id = match source_id_result {
Some(value) => value,
None => return,
};
let source_result = crate::GitSourceConfig::remote(source_id, "ssh://git@example.invalid/howtos.git");
assert!(source_result.is_ok());
let source = match source_result {
Ok(value) => value,
Err(_) => return,
};
assert!(backend.ensure_mirror(&source).is_ok());
let calls = runner.calls.borrow();
assert_eq!(calls[0].operation, crate::GitOperation::CloneMirror);
assert_eq!(calls[0].arguments[0], std::ffi::OsString::from("clone"));
assert_eq!(calls[0].arguments[1], std::ffi::OsString::from("--mirror"));
assert_eq!(calls[0].arguments[2], std::ffi::OsString::from("--"));
assert!(!calls[0].arguments.contains(&std::ffi::OsString::from("--no-local")));
let _ = std::fs::remove_dir_all(root);
return;
}
#[test]
fn process_errors_do_not_expose_remote_location_or_credentials() {
let root = test_root("redaction");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let runner = RecordingRunner::failing(crate::GitBackendError::GitCommandFailed { operation: crate::GitOperation::CloneMirror, exit_code: Some(128) });
let backend = crate::GitBackend::with_runner(config, Box::new(runner));
let source_id_result = source_id("snippets");
assert!(source_id_result.is_some());
let source_id = match source_id_result {
Some(value) => value,
None => return,
};
let source_result = crate::GitSourceConfig::remote(source_id, "https://user:secret@example.invalid/snippets.git");
assert!(source_result.is_ok());
let source = match source_result {
Ok(value) => value,
Err(_) => return,
};
let result = backend.ensure_mirror(&source);
assert!(result.is_err());
let error = match result {
Ok(_) => return,
Err(value) => value,
};
let display = error.to_string();
assert!(!display.contains("secret"));
assert!(!display.contains("example.invalid"));
assert_eq!(error, crate::GitBackendError::GitCommandFailed { operation: crate::GitOperation::CloneMirror, exit_code: Some(128) });
let _ = std::fs::remove_dir_all(root);
return;
}
#[test]
fn existing_mirror_is_inspected_without_clone() {
let root = test_root("existing-mirror");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let runner = RecordingRunner::successful();
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
let source_result = source_id("pgr");
assert!(source_result.is_some());
let source_id = match source_result {
Some(value) => value,
None => return,
};
let repository_path = backend.repository_path(&source_id);
assert!(std::fs::create_dir_all(&repository_path).is_ok());
let source_result = crate::GitSourceConfig::remote(source_id, "https://example.invalid/pgr.git");
assert!(source_result.is_ok());
let source = match source_result {
Ok(value) => value,
Err(_) => return,
};
let result = backend.ensure_mirror(&source);
assert!(result.is_ok());
let mirror = match result {
Ok(value) => value,
Err(_) => return,
};
assert_eq!(mirror.status(), crate::GitMirrorStatus::Existing);
let calls = runner.calls.borrow();
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].operation, crate::GitOperation::InspectMirror);
let _ = std::fs::remove_dir_all(root);
return;
}
#[test]
fn invalid_existing_mirror_is_rejected_without_deleting_it() {
let root = test_root("invalid-mirror");
let config_result = crate::GitBackendConfig::new(root.clone());
assert!(config_result.is_ok());
let config = match config_result {
Ok(value) => value,
Err(_) => return,
};
let runner = RecordingRunner::with_inspection_stdout(b"false\n");
let backend = crate::GitBackend::with_runner(config, Box::new(runner));
let source_result = source_id("pgr");
assert!(source_result.is_some());
let source_id = match source_result {
Some(value) => value,
None => return,
};
let repository_path = backend.repository_path(&source_id);
assert!(std::fs::create_dir_all(&repository_path).is_ok());
let result = backend.inspect_mirror(&source_id);
assert_eq!(result, Err(crate::GitBackendError::InvalidMirror));
assert!(repository_path.is_dir());
let _ = std::fs::remove_dir_all(root);
return;
}

View File

@@ -0,0 +1,14 @@
// file: crates/knowledge-sync-api/src/event_source.rs
// version: 1
/// Producer of synchronization signals.
///
/// Concrete implementations may represent explicit queues, polling, hooks, webhooks or other
/// event mechanisms. The scheduling/runtime model remains outside this passive contract.
pub trait RepositoryEventSource {
/// Implementation-specific recoverable error.
type Error;
/// Return the next available signal, or `None` when no signal is currently available.
fn next_signal(&mut self) -> Result<Option<crate::RepositorySyncSignal>, Self::Error>;
}

View File

@@ -1,57 +1,20 @@
// file: crates/knowledge-sync-api/src/lib.rs
// version: 2
// version: 3
//! Contracts for provider-neutral repository synchronization signals.
//!
//! A signal requests reconciliation of a source. It is not authoritative content and does not
//! replace resolution of the target Git revision by the Git repository contract.
/// Coarse origin of a synchronization request.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RepositorySyncCause {
/// An explicit request from an operator or application use case.
Explicit,
/// A periodic reconciliation independent of external hooks.
Periodic,
/// A non-authoritative external signal such as a Git hook, webhook or filesystem watcher.
ExternalSignal,
}
/// Request to reconcile one logical content source.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepositorySyncSignal {
source_id: knowledge_domain_lib::SourceId,
cause: crate::RepositorySyncCause,
}
impl crate::RepositorySyncSignal {
/// Build a synchronization signal for one source.
pub fn new(source_id: knowledge_domain_lib::SourceId, cause: crate::RepositorySyncCause) -> crate::RepositorySyncSignal {
return crate::RepositorySyncSignal { source_id, cause };
}
/// Return the source that must be reconciled.
pub fn source_id(&self) -> &knowledge_domain_lib::SourceId {
return &self.source_id;
}
/// Return the coarse trigger origin.
pub fn cause(&self) -> crate::RepositorySyncCause {
return self.cause;
}
}
mod event_source;
mod signal;
/// Producer of synchronization signals.
///
/// Concrete implementations may represent explicit queues, polling, hooks, webhooks or other
/// event mechanisms. The scheduling/runtime model remains outside this passive contract.
pub trait RepositoryEventSource {
/// Implementation-specific recoverable error.
type Error;
/// Return the next available signal, or `None` when no signal is currently available.
fn next_signal(&mut self) -> Result<Option<crate::RepositorySyncSignal>, Self::Error>;
}
pub use self::event_source::RepositoryEventSource;
/// Coarse origin of a synchronization request.
pub use self::signal::RepositorySyncCause;
/// Request to reconcile one logical content source.
pub use self::signal::RepositorySyncSignal;
#[cfg(test)]
#[path = "../unit_tests/lib.rs"]

View File

@@ -0,0 +1,37 @@
// file: crates/knowledge-sync-api/src/signal.rs
// version: 1
/// Coarse origin of a synchronization request.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RepositorySyncCause {
/// An explicit request from an operator or application use case.
Explicit,
/// A periodic reconciliation independent of external hooks.
Periodic,
/// A non-authoritative external signal such as a Git hook, webhook or filesystem watcher.
ExternalSignal,
}
/// Request to reconcile one logical content source.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepositorySyncSignal {
source_id: knowledge_domain_lib::SourceId,
cause: crate::RepositorySyncCause,
}
impl crate::RepositorySyncSignal {
/// Build a synchronization signal for one source.
pub fn new(source_id: knowledge_domain_lib::SourceId, cause: crate::RepositorySyncCause) -> crate::RepositorySyncSignal {
return crate::RepositorySyncSignal { source_id, cause };
}
/// Return the source that must be reconciled.
pub fn source_id(&self) -> &knowledge_domain_lib::SourceId {
return &self.source_id;
}
/// Return the coarse trigger origin.
pub fn cause(&self) -> crate::RepositorySyncCause {
return self.cause;
}
}

View File

@@ -0,0 +1,204 @@
<!-- file: deltas/0.0.2/alpha.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.1.fix.1 — restructuration du plan de release
## Base requise
Partir de l'état `0.0.2-alpha.1` après application et commit du history :
```text
deltas/0.0.2/alpha.1.md présent et historique
history/0.0.2/alpha.1.md FAIL
```
Le delta `alpha.1` historique ne doit pas être réécrit.
La version devient :
```text
0.0.2-alpha.1.fix.1
```
## Objet
Corriger uniquement le défaut documentaire observé pendant la revue d'`alpha.1` :
- réaligner le plan `0.0.2` sur le patron de pilotage éprouvé par le plan `0.0.1` ;
- replacer toutes les tranches sous `## Trajectoire prévisionnelle` avec des titres `###` ;
- restaurer un `## Scope` global distinct du `## Hors scope` ;
- ajouter `## État acquis et questions ouvertes` ;
- ajouter `## Déclencheurs de REPLAN` ;
- ajouter `## Revue d'horizon` ;
- ordonner les sections de manière cohérente avec la fondation ;
- ajouter une gate explicite empêchant une nouvelle dérive structurelle du plan.
Le fix ne change ni le choix du backend `git`, ni l'ownership de `knowledge-git-lib`, ni le layout mirror/snapshot, ni le scope fonctionnel prévu de `0.0.2`.
Aucun code fonctionnel n'est ajouté.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.2/alpha.1.fix.1.md
```
DELETE : aucun.
Le history `history/0.0.2/alpha.1.md` doit déjà être présent dans la base mais n'est pas inclus dans ce delta.
Aucun fichier sous `crates/` ou `Web/` n'est modifié.
## Structure attendue du plan
Le plan doit présenter, dans cet ordre :
```text
## Statut et sources
## Mission
## État acquis et questions ouvertes
## Scope
## Hors scope
## Stratégie de validation
## Trajectoire prévisionnelle
### alpha.1 — ...
### alpha.2 — ...
### alpha.3 — ...
### alpha.4 — ...
### beta.1 — ...
### rc.1 — ...
### rel.1 — ...
## Déclencheurs de REPLAN
## Clôture et transition
## Revue d'horizon
```
Les détails des tranches restent des forecasts et ne doivent pas être confondus avec le scope global de la release.
## Gate opérateur
Après application du history `alpha.1`, commit/push de cet état, puis application du présent delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.1.fix.1'
grep -F 'version = "0.0.2-alpha.1.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.1.md | grep -Fx 'FAIL'
git diff --check
python3 - <<'PY'
from pathlib import Path
import re
path = Path("docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md")
text = path.read_text(encoding="utf-8")
lines = text.splitlines()
assert lines[0] == "<!-- file: docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md -->"
assert lines[1] == "<!-- version: 2 -->"
required_h2 = [
"## Statut et sources",
"## Mission",
"## État acquis et questions ouvertes",
"## Scope",
"## Hors scope",
"## Stratégie de validation",
"## Trajectoire prévisionnelle",
"## Déclencheurs de REPLAN",
"## Clôture et transition",
"## Revue d'horizon",
]
positions = [lines.index(heading) for heading in required_h2]
assert positions == sorted(positions), positions
trajectory_start = lines.index("## Trajectoire prévisionnelle")
replan_start = lines.index("## Déclencheurs de REPLAN")
trajectory = lines[trajectory_start + 1:replan_start]
for phase in ("alpha.1", "alpha.2", "alpha.3", "alpha.4", "beta.1", "rc.1", "rel.1"):
assert any(line.startswith(f"### {phase} — ") for line in trajectory), phase
for line in lines:
assert re.fullmatch(r"## (?:alpha|beta|rc|rel)\\..*", line) is None, line
for phrase in (
"0.0.2-alpha.1` est `FAIL`",
"alpha.1.fix.1",
"REPLAN obligatoire si",
"État courant : **non réalisée**.",
"Aucune version postérieure à `0.0.2` n'est réservée",
):
assert phrase in text, phrase
print("Knowledge 0.0.2-alpha.1.fix.1 plan structure audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
git diff --name-only -- crates Web
git add -A
git diff --cached --check
git status --short
```
Attendus spécifiques :
- `Knowledge 0.0.2-alpha.1.fix.1 plan structure audit: clean` ;
- `git diff --name-only -- crates Web` vide ;
- aucun `history/0.0.2/alpha.1.fix.1.md` avant réception des résultats opérateur ;
- aucun changement de comportement Git ou Web.
Les warnings Sass déjà observés dans Bootstrap ne constituent pas à eux seuls un `FAIL` si `npm run build` reste `PASS`.
## Revue opérateur complémentaire
Vérifier que :
- le plan se lit d'abord au niveau release (`Mission`, `Scope`, `Hors scope`) puis au niveau tranches sous `Trajectoire prévisionnelle` ;
- les déclencheurs de REPLAN sont suffisamment explicites pour empêcher l'élargissement silencieux d'une tranche ;
- les questions ouvertes restent différées jusqu'à la tranche qui les consomme ;
- la revue d'horizon ne pré-réserve pas de version suivante ;
- aucune décision technique validée par l'étude Git n'a été changée par simple restructuration documentaire.
## Transition
### PASS
Produire `history/0.0.2/alpha.1.fix.1.md`, appliquer ce history, committer/pousser le fix, puis seulement préparer `0.0.2-alpha.2`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans `history/0.0.2/alpha.1.fix.1.md` et rester dans le scope `alpha.1` avec `alpha.1.fix.2` si nécessaire.

185
deltas/0.0.2/alpha.1.md Normal file
View File

@@ -0,0 +1,185 @@
<!-- file: deltas/0.0.2/alpha.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.1 — adoption PGR et cadrage Git/synchronisation
## Baseline requise
Partir de la stable autoritaire :
```text
tag Knowledge v0.0.1
commit Knowledge 98ec46fe73bd50dacbe6be220650d87dd2000b74
archive SHA-256 1f0b2cfaecee1cbee621abb9c3c95811773ebfc17c374c4369fdc220bcf7d72d
publish report sasedev-knowledge-0.0.1-publish.001-report.md = PASS
```
Le prompt exécuté est :
```text
prompts/002-V0_0_2_START_PROMPT.md
```
La version devient :
```text
0.0.2-alpha.1
```
## Objet
Fermer les décisions obligatoires de démarrage de `0.0.2` avant toute implémentation Git fonctionnelle :
- vérifier et adopter explicitement la stable PGR courante `v0.1.3` dans son scope applicable ;
- activer `230-ADOPTION` pour la migration PGR du projet existant ;
- préserver l'historique `0.0.1` construit sous PGR `v0.1.1` ;
- documenter l'inventaire des contrats Git/sync existants ;
- sélectionner le processus `git` contrôlé après comparaison avec `gix` et `git2` ;
- attribuer à la future `knowledge-git-lib` l'ownership de la configuration physique et du backend Git ;
- fixer une racine d'état Git généré hors du dépôt source ;
- établir le plan versionné `0.0.2` et sa première gate.
Aucun runtime Git, aucune crate et aucune dépendance ne sont ajoutés dans cette tranche.
## Adoption PGR
La baseline PGR applicable devient :
```text
PGR tag v0.1.3
PGR commit fe80dbbb737390a7ea99df27d979bd59ccfb86e3
PGR archive SHA-256 f3a7dffd5fe0fd3542e6b5207fda0b94bca2bd05bd5a54542d0a08d635a5d6ee
PGR publish report pgr-0.1.3-publish.001-report.md = PASS
```
Le mapping durable est enregistré dans `docs/architecture/001-PROJECT_PROFILE.md`.
Les changements PGR `0.1.2`/`0.1.3` sur Java/Maven/Gradle/Android ne rendent pas ces profils applicables à Knowledge. Le `DEFER` Tauri reste inchangé. Aucun `OVERRIDE` n'est introduit.
## Décision Git
L'étude :
```text
docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
```
retient le client Git externe piloté via `std::process::Command`, sans shell.
Le layout retenu est conceptuellement :
```text
<git-state-root>/
hooks-disabled/
repositories/<source-key>.git/
snapshots/<source-key>/<resolved-revision>/
```
Aucun de ces chemins n'est créé par `alpha.1`.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
README.md
RULES.md
docs/000-README.md
docs/architecture/001-PROJECT_PROFILE.md
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
deltas/0.0.2/alpha.1.md
```
DELETE : aucun.
Aucun fichier sous `crates/` ou `Web/` n'est modifié.
Aucun history `0.0.2-alpha.1` n'est inclus : il doit refléter les preuves opérateur après application.
## Gate opérateur
Après application du delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.1'
grep -F 'version = "0.0.2-alpha.1"' Cargo.toml
git diff --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
```
Puis rejouer les contrôles de manifest Vite et les trois smokes HTTP selon les commandes déjà possédées par la gate stable `0.0.1`.
Contrôles spécifiques de cette tranche :
```bash
test -f docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
test -f docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
test -f deltas/0.0.2/alpha.1.md
test ! -e history/0.0.2/alpha.1.md
grep -F 'PGR stable tag v0.1.3' docs/architecture/001-PROJECT_PROFILE.md
grep -F '`230-ADOPTION`' docs/architecture/001-PROJECT_PROFILE.md
grep -F 'processus `git` contrôlé' docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
grep -F 'repositories/<source-key>.git' docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
grep -F 'snapshots/<source-key>/<resolved-revision>' docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
git diff --name-only -- crates Web
git diff --cached --check
```
`git diff --name-only -- crates Web` doit être vide pour cette tranche.
Le `Cargo.lock` peut être généré ou modifié localement par Cargo mais reste ignoré/non suivi conformément à la stable `0.0.1`. Le `package-lock.json` reste versionné.
## Transition
### PASS
Produire séparément :
```text
history/0.0.2/alpha.1.md
```
avec les résultats réels, l'appliquer, puis commit/push `0.0.2-alpha.1` avant préparation de `alpha.2`.
### FAIL
Produire le history réel `FAIL`, le committer, puis corriger uniquement cette tranche avec :
```text
0.0.2-alpha.1.fix.1
```
Ne pas préparer `alpha.2` tant que la tranche courante n'est pas fermée conformément au workflow.

View File

@@ -0,0 +1,280 @@
<!-- file: deltas/0.0.2/alpha.2.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.2.fix.1 — correction de gate, formatage et construction interne
## Base requise
Partir de `0.0.2-alpha.2` après application et commit du history réel :
```text
deltas/0.0.2/alpha.2.md présent et historique
history/0.0.2/alpha.2.md FAIL
```
Le delta `alpha.2` historique ne doit pas être réécrit.
La version devient :
```text
0.0.2-alpha.2.fix.1
```
## Objet
Corriger uniquement les défauts révélés par la gate opérateur d'`alpha.2` :
- supprimer la ligne vide terminale détectée par `git diff --check` dans la matrice de validation ;
- empêcher l'audit Markdown embarqué de traiter `node_modules` ou d'autres arbres générés comme contenu gouverné ;
- appliquer le formatage attendu par `cargo fmt` aux quatre fichiers Rust signalés ;
- construire `GitSourceKey` depuis son module propriétaire au lieu de tenter d'utiliser son constructeur tuple privé via le réexport `crate::GitSourceKey` ;
- conserver le champ tuple privé et ne pas élargir l'API publique ;
- documenter le `FAIL` d'`alpha.2` et la portée strictement corrective de ce fix.
Le fix n'ajoute aucune capacité Git. `refresh`, résolution vers commit et snapshot/worktree restent réservés à `alpha.3`.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
crates/knowledge-git-lib/src/backend.rs
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/unit_tests/backend.rs
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.2/alpha.2.fix.1.md
```
DELETE : aucun.
Le history `history/0.0.2/alpha.2.md` appartient à la base requise mais n'est pas inclus dans ce delta.
Aucun fichier sous `Web/`, `crates/knowledge-git-api/` ou `crates/knowledge-sync-api/` n'est modifié.
## Corrections attendues
### Hygiène documentaire
`docs/validation/001-VALIDATION_MATRIX.md` doit terminer par une seule fin de ligne, sans ligne vide additionnelle produisant un warning `git diff --check`.
La gate du fix contrôle les Markdown du workspace mais ignore explicitement les arbres générés/non gouvernés :
```text
.git
node_modules
target
builds
```
La présence d'un README provenant d'une dépendance npm ne doit donc plus faire échouer la gouvernance documentaire du projet.
### Rustfmt
Les fichiers signalés par la gate initiale sont remis dans la forme attendue par `rustfmt.toml` :
```text
crates/knowledge-git-lib/src/backend.rs
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/unit_tests/backend.rs
```
Aucun changement fonctionnel autre que la correction de visibilité ci-dessous n'est introduit.
### Construction de `GitSourceKey`
`GitSourceKey` conserve son champ tuple privé :
```text
pub struct GitSourceKey(String);
```
Sa méthode `from_source_id`, définie dans le module `backend`, construit le type avec `Self(value)` depuis son propre `impl`. Le réexport public reste destiné à nommer le type, pas à contourner la visibilité de son champ interne.
## Gate opérateur
Après application du history `alpha.2`, commit/push de cet état, puis application du présent delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.2.fix.1'
grep -F 'version = "0.0.2-alpha.2.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.2.md | grep -Fx 'FAIL'
git diff --check
python3 - <<'PY'
from pathlib import Path
import re
import tomllib
required = (
"crates/knowledge-git-lib/Cargo.toml",
"crates/knowledge-git-lib/src/backend.rs",
"crates/knowledge-git-lib/src/error.rs",
"crates/knowledge-git-lib/src/lib.rs",
"crates/knowledge-git-lib/unit_tests/backend.rs",
"deltas/0.0.2/alpha.2.md",
"deltas/0.0.2/alpha.2.fix.1.md",
"history/0.0.2/alpha.2.md",
)
for value in required:
assert Path(value).is_file(), value
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
manifest = tomllib.loads(Path("crates/knowledge-git-lib/Cargo.toml").read_text(encoding="utf-8"))
assert "crates/knowledge-git-lib" in root["workspace"]["members"]
assert set(manifest["dependencies"]) == {"knowledge-domain-lib"}, manifest["dependencies"]
backend = Path("crates/knowledge-git-lib/src/backend.rs").read_text(encoding="utf-8")
error = Path("crates/knowledge-git-lib/src/error.rs").read_text(encoding="utf-8")
assert "return Self(value);" in backend
assert "return crate::GitSourceKey(value);" not in backend
assert "return crate::backend::GitSourceKey(value);" not in backend
assert "pub struct GitSourceKey(String);" in backend
for phrase in (
"std::process::Command::new",
'command.env("GIT_TERMINAL_PROMPT", "0")',
'command.env_remove("GIT_DIR")',
'command.env_remove("GIT_WORK_TREE")',
'command.env_remove("GIT_INDEX_FILE")',
'command.env_remove("GIT_OBJECT_DIRECTORY")',
'command.env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES")',
'command.env("GIT_CONFIG_KEY_0", "core.hooksPath")',
'std::ffi::OsString::from("--mirror")',
'std::ffi::OsString::from("--no-local")',
'std::ffi::OsString::from("--")',
'std::ffi::OsString::from("--is-bare-repository")',
):
assert phrase in backend, phrase
for forbidden in (
'Command::new("sh")',
'Command::new("bash")',
'Command::new("cmd")',
'Command::new("powershell")',
"stderr: String",
"arguments: Vec<String>",
"refresh(",
"resolve_revision(",
"worktree add",
"knowledge-app-lib",
):
assert forbidden not in backend + error, forbidden
for path in Path("crates/knowledge-git-lib").rglob("*.rs"):
text = path.read_text(encoding="utf-8")
for forbidden in ("unwrap()", "expect(", "panic!("):
assert forbidden not in text, (path, forbidden)
ignored_parts = {".git", "node_modules", "target", "builds"}
checked_markdown = 0
for path in Path(".").rglob("*.md"):
if any(part in ignored_parts for part in path.parts):
continue
lines = path.read_text(encoding="utf-8").splitlines()
assert lines[0] == f"<!-- file: {path.as_posix()} -->", path
assert re.fullmatch(r"<!-- version: [1-9][0-9]* -->", lines[1]), path
checked_markdown += 1
assert checked_markdown > 0
print(f"Knowledge 0.0.2-alpha.2.fix.1 structural audit: clean ({checked_markdown} Markdown file(s))")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.2.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.2.fix.1-metadata.json").read_text(encoding="utf-8"))
packages = {package["name"] for package in metadata["packages"]}
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert packages == expected, packages
assert len(metadata["workspace_members"]) == 8, metadata["workspace_members"]
print("Knowledge 0.0.2-alpha.2.fix.1 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-git-api crates/knowledge-sync-api)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Résultats attendus
La gate doit notamment produire :
```text
Knowledge 0.0.2-alpha.2.fix.1 structural audit: clean (... Markdown file(s))
Rust test-layout audit: clean (8 crate(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
Knowledge 0.0.2-alpha.2.fix.1 Cargo metadata audit: clean
```
`cargo fmt --all -- --check` doit être silencieux et `cargo check`, Clippy et les deux suites de tests doivent tous terminer avec succès.
La suite ciblée `knowledge-git-lib` doit exécuter `7` tests unitaires `PASS`, sauf modification explicitement justifiée du nombre de tests dans un fix ultérieur.
## Revue opérateur complémentaire
Vérifier que :
- la correction de visibilité n'a pas rendu public l'état interne de `GitSourceKey` ;
- l'audit Markdown ne dépend plus de l'absence physique de `node_modules` ;
- aucun comportement de `refresh`, résolution ou snapshot n'est apparu dans le fix ;
- aucune nouvelle dépendance Cargo n'a été introduite ;
- le fix ne change pas l'architecture validée d'`alpha.2`.
## Transition
### PASS
Produire `history/0.0.2/alpha.2.fix.1.md`, appliquer ce history, committer/pousser le fix, puis seulement préparer `0.0.2-alpha.3`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans `history/0.0.2/alpha.2.fix.1.md` et rester dans le scope `alpha.2` avec `alpha.2.fix.2` si nécessaire, sauf déclencheur de REPLAN explicitement satisfait.

View File

@@ -0,0 +1,183 @@
<!-- file: deltas/0.0.2/alpha.2.fix.2.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.2.fix.2 — retour explicite Clippy
## Base requise
Partir de `0.0.2-alpha.2.fix.1` après application et commit du history réel :
```text
deltas/0.0.2/alpha.2.md présent et historique
deltas/0.0.2/alpha.2.fix.1.md présent et historique
history/0.0.2/alpha.2.md FAIL
history/0.0.2/alpha.2.fix.1.md FAIL
```
Les deltas historiques ne doivent pas être réécrits.
La version devient :
```text
0.0.2-alpha.2.fix.2
```
## Objet
Corriger uniquement le dernier défaut révélé par la gate opérateur de `alpha.2.fix.1` :
```text
clippy::implicit_return
crates/knowledge-git-lib/unit_tests/backend.rs:85
```
La closure de validation du `source-key` doit rendre explicitement son booléen avec `return` conformément aux lints workspace.
Le fix ne modifie aucune logique du backend Git et n'ajoute aucune capacité. `refresh`, résolution vers commit et snapshot/worktree restent réservés à `alpha.3`.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
crates/knowledge-git-lib/unit_tests/backend.rs
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.2/alpha.2.fix.2.md
```
DELETE : aucun.
Le history `history/0.0.2/alpha.2.fix.1.md` appartient à la base requise mais n'est pas inclus dans ce delta.
Aucun fichier sous `Web/`, `crates/knowledge-git-api/` ou `crates/knowledge-sync-api/` n'est modifié. Les fichiers fonctionnels `src/backend.rs`, `src/error.rs` et `src/lib.rs` de `knowledge-git-lib` restent inchangés.
## Correction attendue
Le test `source_key_never_interpolates_raw_source_identifier_as_a_path` doit remplacer le retour implicite de la closure par un retour explicite :
```text
|character| return character.is_ascii_alphanumeric() || character == '-'
```
Aucun autre changement de comportement n'est attendu.
## Gate opérateur
Après application du history `alpha.2.fix.1`, commit/push de cet état, puis application du présent delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.2.fix.2'
grep -F 'version = "0.0.2-alpha.2.fix.2"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.2.fix.1.md | grep -Fx 'FAIL'
git diff --check
python3 - <<'PY'
from pathlib import Path
import tomllib
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
manifest = tomllib.loads(Path("crates/knowledge-git-lib/Cargo.toml").read_text(encoding="utf-8"))
assert len(root["workspace"]["members"]) == 8
assert "crates/knowledge-git-lib" in root["workspace"]["members"]
assert set(manifest["dependencies"]) == {"knowledge-domain-lib"}
test = Path("crates/knowledge-git-lib/unit_tests/backend.rs").read_text(encoding="utf-8")
assert "|character| return character.is_ascii_alphanumeric() || character == '-'" in test
assert "|character| character.is_ascii_alphanumeric() || character == '-'" not in test
for path in (
"crates/knowledge-git-lib/src/backend.rs",
"crates/knowledge-git-lib/src/error.rs",
"crates/knowledge-git-lib/src/lib.rs",
):
assert Path(path).is_file(), path
backend = Path("crates/knowledge-git-lib/src/backend.rs").read_text(encoding="utf-8")
error = Path("crates/knowledge-git-lib/src/error.rs").read_text(encoding="utf-8")
for forbidden in ("refresh(", "resolve_revision(", "worktree add", "knowledge-app-lib"):
assert forbidden not in backend + error, forbidden
print("Knowledge 0.0.2-alpha.2.fix.2 structural audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.2.fix.2-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.2.fix.2-metadata.json").read_text(encoding="utf-8"))
packages = {package["name"] for package in metadata["packages"]}
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert packages == expected, packages
assert len(metadata["workspace_members"]) == 8, metadata["workspace_members"]
print("Knowledge 0.0.2-alpha.2.fix.2 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-git-api crates/knowledge-sync-api)"
test -z "$(git diff --name-only -- crates/knowledge-git-lib/src/backend.rs crates/knowledge-git-lib/src/error.rs crates/knowledge-git-lib/src/lib.rs)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Résultat attendu
Le point critique est :
```text
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
qui doit désormais être `PASS` sans warning ni erreur.
Le test ciblé doit conserver :
```text
7 passed; 0 failed
```
Un `PASS` complet ferme `alpha.2` et autorise uniquement la préparation d'`alpha.3`.

324
deltas/0.0.2/alpha.2.md Normal file
View File

@@ -0,0 +1,324 @@
<!-- file: deltas/0.0.2/alpha.2.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.2 — contrats minimaux et backend process Git
## Base requise
Partir de `0.0.2-alpha.1.fix.1` après application de son history `PASS`, commit et push :
```text
history/0.0.2/alpha.1.md FAIL
history/0.0.2/alpha.1.fix.1.md PASS
```
Le workspace doit être propre avant application du présent delta.
La version devient :
```text
0.0.2-alpha.2
```
## Objet
Matérialiser la première responsabilité runtime Git réelle sans anticiper la tranche suivante :
- créer `knowledge-git-lib` comme crate d'implémentation réutilisable ;
- posséder une racine d'état explicitement fournie au backend ;
- posséder la configuration physique d'une source locale ou remote ;
- dériver une `source-key` sûre sans interpoler directement `SourceId` ou URL dans le filesystem ;
- encapsuler `std::process::Command` derrière un runner interne testable, sans shell ;
- préparer les répertoires `hooks-disabled/`, `repositories/` et `snapshots/` ;
- initialiser un mirror avec `git clone --mirror`, et `--no-local` pour une source locale ;
- inspecter un mirror existant comme repository bare avant réutilisation ;
- ne pas supprimer/recréer silencieusement un mirror invalide ;
- ne pas exposer URL, arguments, stderr ou credentials dans les erreurs publiques du runner.
`refresh`, résolution vers commit et snapshot/worktree fonctionnel restent réservés à `alpha.3`.
## Scope
ADD :
```text
crates/knowledge-git-lib/Cargo.toml
crates/knowledge-git-lib/src/backend.rs
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/unit_tests/backend.rs
deltas/0.0.2/alpha.2.md
```
REPLACE :
```text
Cargo.toml
README.md
RULES.md
VERSION
docs/architecture/001-PROJECT_PROFILE.md
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
DELETE : aucun.
Le history `history/0.0.2/alpha.1.fix.1.md` appartient à la base requise mais n'est pas inclus dans ce delta.
Aucun fichier sous `Web/` n'est modifié.
## Contrat de `knowledge-git-lib`
La crate dépend uniquement de `knowledge-domain-lib` dans cette tranche.
Elle expose :
```text
GitBackend
GitBackendConfig
GitSourceConfig
GitSourceKind
GitSourceKey
GitMirror
GitMirrorStatus
GitBackendError
GitOperation
```
`knowledge-git-api` reste inchangée : son trait `GitRepository` possède déjà `refresh` et `resolve_revision`, qui ne doivent pas être implémentés artificiellement avant la tranche qui matérialise ces comportements.
La dépendance et l'implémentation explicite de ce trait seront introduites lorsque le backend possédera effectivement les opérations requises.
## Layout d'état
Une configuration valide possède obligatoirement une racine d'état non vide :
```text
<git-state-root>/
hooks-disabled/
repositories/
<source-key>.git/
snapshots/
```
Le répertoire `snapshots/` est réservé dès maintenant par le layout mais aucun snapshot n'est créé dans `alpha.2`.
La `source-key` est obtenue par encodage hexadécimal déterministe des octets UTF-8 du `SourceId`, préfixé par `source-`. Cette stratégie évite les collisions entre octets distincts et interdit qu'un séparateur de chemin, `..`, une URL ou un credential provenant de l'entrée logique devienne un composant de chemin brut.
## Runner Git contrôlé
Le runner système :
- utilise `std::process::Command` directement ;
- passe chaque argument séparément ;
- pose `GIT_TERMINAL_PROMPT=0` ;
- retire les variables `GIT_*` qui peuvent rediriger implicitement repository, worktree, index ou object database ;
- force `core.hooksPath` vers le répertoire vide contrôlé via l'environnement de configuration Git ;
- transforme un échec de spawn ou exit non nul en erreur récupérable ;
- ne copie jamais arguments, source, stdout/stderr ou credentials dans l'erreur publique.
Les sources locales utilisent :
```text
git clone --mirror --no-local -- <source> <mirror>
```
Les sources remote utilisent :
```text
git clone --mirror -- <source> <mirror>
```
Un mirror existant est inspecté avec :
```text
git --git-dir <mirror> rev-parse --is-bare-repository
```
Une réponse différente de `true` produit une erreur sans suppression destructive.
## Tests de la tranche
Les tests unitaires de `knowledge-git-lib` utilisent un runner enregistré injecté uniquement sous `cfg(test)` pour vérifier sans réseau :
- rejet des chemins de configuration vides ;
- dérivation sûre de `source-key` ;
- arguments exacts de clone local ;
- absence de `--no-local` pour un remote ;
- redaction des erreurs contenant potentiellement une URL avec credentials ;
- inspection d'un mirror existant sans clone ;
- refus d'un mirror invalide sans suppression.
Les tests réels sur repositories Git temporaires appartiennent à `alpha.3`, avec refresh, résolution et snapshot.
## Gate opérateur
Après application du history `alpha.1.fix.1`, commit/push de cet état, puis application du présent delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.2'
grep -F 'version = "0.0.2-alpha.2"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.1.fix.1.md | grep -Fx 'PASS'
git diff --check
python3 - <<'PY'
from pathlib import Path
import re
import tomllib
required = (
"crates/knowledge-git-lib/Cargo.toml",
"crates/knowledge-git-lib/src/backend.rs",
"crates/knowledge-git-lib/src/error.rs",
"crates/knowledge-git-lib/src/lib.rs",
"crates/knowledge-git-lib/unit_tests/backend.rs",
"deltas/0.0.2/alpha.2.md",
)
for value in required:
assert Path(value).is_file(), value
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
manifest = tomllib.loads(Path("crates/knowledge-git-lib/Cargo.toml").read_text(encoding="utf-8"))
assert "crates/knowledge-git-lib" in root["workspace"]["members"]
assert set(manifest["dependencies"]) == {"knowledge-domain-lib"}, manifest["dependencies"]
backend = Path("crates/knowledge-git-lib/src/backend.rs").read_text(encoding="utf-8")
error = Path("crates/knowledge-git-lib/src/error.rs").read_text(encoding="utf-8")
for phrase in (
"std::process::Command::new",
'command.env("GIT_TERMINAL_PROMPT", "0")',
'command.env_remove("GIT_DIR")',
'command.env_remove("GIT_WORK_TREE")',
'command.env_remove("GIT_INDEX_FILE")',
'command.env_remove("GIT_OBJECT_DIRECTORY")',
'command.env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES")',
'command.env("GIT_CONFIG_KEY_0", "core.hooksPath")',
'std::ffi::OsString::from("--mirror")',
'std::ffi::OsString::from("--no-local")',
'std::ffi::OsString::from("--")',
'std::ffi::OsString::from("--is-bare-repository")',
):
assert phrase in backend, phrase
for forbidden in (
'Command::new("sh")',
'Command::new("bash")',
'Command::new("cmd")',
'Command::new("powershell")',
"stderr: String",
"arguments: Vec<String>",
):
assert forbidden not in backend + error, forbidden
for forbidden in ("refresh(", "resolve_revision(", "worktree add", "knowledge-app-lib"):
assert forbidden not in backend, forbidden
for path in Path("crates/knowledge-git-lib").rglob("*.rs"):
text = path.read_text(encoding="utf-8")
for forbidden in ("unwrap()", "expect(", "panic!("):
assert forbidden not in text, (path, forbidden)
for path in Path(".").rglob("*.md"):
if ".git" in path.parts:
continue
lines = path.read_text(encoding="utf-8").splitlines()
assert lines[0] == f"<!-- file: {path.as_posix()} -->", path
assert re.fullmatch(r"<!-- version: [1-9][0-9]* -->", lines[1]), path
print("Knowledge 0.0.2-alpha.2 structural audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.2-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.2-metadata.json").read_text(encoding="utf-8"))
packages = {package["name"] for package in metadata["packages"]}
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert packages == expected, packages
assert len(metadata["workspace_members"]) == 8, metadata["workspace_members"]
print("Knowledge 0.0.2-alpha.2 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-git-api crates/knowledge-sync-api)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Résultats attendus
La gate doit notamment produire :
```text
Knowledge 0.0.2-alpha.2 structural audit: clean
Rust test-layout audit: clean (8 crate(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
Knowledge 0.0.2-alpha.2 Cargo metadata audit: clean
```
La suite ciblée `knowledge-git-lib` doit exécuter `7` tests unitaires `PASS`.
`git diff --name-only -- Web` et le diff ciblant les deux crates `-api` doivent rester vides.
Le staging final doit contenir uniquement le scope annoncé par ce delta. Aucun `history/0.0.2/alpha.2.md` ne doit exister avant réception des résultats opérateur.
## Revue opérateur complémentaire
Vérifier que :
- `knowledge-git-lib` possède bien une responsabilité d'implémentation et n'est pas une crate placeholder ;
- le runner ne passe jamais par un shell ;
- les erreurs process restent exploitables sans recopier des informations sensibles ;
- le layout généré reste reconstructible et distinct du stockage métier ;
- `knowledge-git-api` n'est pas artificiellement étendue pour des opérations qui n'existent pas encore ;
- `alpha.3` reste propriétaire du refresh, de la résolution et du snapshot réel.
## Transition
### PASS
Produire `history/0.0.2/alpha.2.md`, appliquer ce history, committer/pousser `0.0.2-alpha.2`, puis seulement préparer `0.0.2-alpha.3`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans `history/0.0.2/alpha.2.md` et rester dans le scope `alpha.2` avec `alpha.2.fix.1` si nécessaire, sauf déclencheur de REPLAN explicitement satisfait.

View File

@@ -0,0 +1,262 @@
<!-- file: deltas/0.0.2/alpha.3.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.3.fix.1 — conformité alpha.3 et REPLAN d'autonomie Knowledge
## Base requise
Partir de `0.0.2-alpha.3` après application de son history réel `FAIL`, commit et push :
```text
deltas/0.0.2/alpha.3.md présent et historique
history/0.0.2/alpha.3.md FAIL
```
Les deltas, histories et prompts antérieurs ne doivent pas être réécrits.
La version devient :
```text
0.0.2-alpha.3.fix.1
```
## Causes du fix
La gate opérateur d'`alpha.3` a confirmé le comportement Git réel mais a relevé trois défauts bloquants :
1. `docs/validation/001-VALIDATION_MATRIX.md` possède une ligne vide terminale rejetée par `git diff --check` ;
2. `crates/knowledge-git-api/unit_tests/lib.rs` n'est pas au format exact attendu par rustfmt ;
3. `crates/knowledge-git-lib/tests/git_read_path.rs` ne possède pas de documentation de crate et échoue sous `-D missing-docs` lors de Clippy.
La revue a aussi déclenché un REPLAN explicite : avant d'ajouter l'orchestration `Changed` / `Unchanged`, Knowledge doit terminer son autonomie normative après l'adoption PGR et auditer l'organisation interne de ses crates.
## Scope technique du fix
Le correctif technique est volontairement minimal :
- retirer la ligne vide terminale fautive de la matrice ;
- appliquer le formatage rustfmt attendu au test de `knowledge-git-api` ;
- ajouter une documentation de crate au test d'intégration `git_read_path` ;
- conserver inchangés le backend Git, ses erreurs, son API publique et les deux scénarios d'intégration réels.
Le comportement prouvé en `alpha.3` reste donc : mirror -> refresh -> résolution -> snapshot/worktree détaché -> validation/réutilisation.
## REPLAN de `0.0.2`
Le plan `docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md` passe en version documentaire `7` et adopte la trajectoire suivante :
```text
alpha.3 refresh / resolve / snapshot FAIL initial -> fix.1
alpha.4 autonomie normative + structure crates
alpha.5 cas d'usage Changed / Unchanged
beta.1 réconciliation + revue d'horizon
rc.1 candidate stable
rel.1 fermeture stable
```
L'ancienne tranche `alpha.4` de synchronisation devient `alpha.5` sans changement de mission fonctionnelle.
## Autonomie PGR enregistrée
`RULES.md` et le profil projet distinguent désormais explicitement :
```text
PGR v0.1.3 = provenance de l'adoption engagée pendant 0.0.2
Knowledge = baseline normative locale après matérialisation
```
Les règles sont :
- ne pas charger/rechercher automatiquement PGR au début de chaque version, session ou tranche ;
- ne pas faire dépendre un futur prompt de démarrage Knowledge d'une « stable PGR courante » ;
- consulter une nouvelle version PGR uniquement lorsqu'une réévaluation/adoption PGR est explicitement inscrite au scope ;
- lors d'une telle réévaluation, comparer la baseline locale puis classifier `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER` ;
- matérialiser/adopter localement uniquement les règles, templates/configurations et audits réellement nécessaires ;
- ne pas recopier mécaniquement l'arborescence PGR lorsqu'un owner Knowledge plus adapté suffit.
Le prompt `prompts/002-V0_0_2_START_PROMPT.md` reste historique et inchangé : sa consultation de PGR appartenait précisément à l'adoption explicitement engagée au démarrage de `0.0.2`.
## Scope futur `alpha.4`
`alpha.4` doit :
- inventorier les owners/contrats PGR `v0.1.3` réellement retenus par Knowledge ;
- produire le mapping durable de leurs décisions et owners locaux ;
- vérifier que gouvernance, workflow, documentation, validation, Rust/Cargo et Web nécessaires sont compréhensibles depuis Knowledge seul ;
- adapter/matérialiser les règles, templates/configurations et audits manquants ;
- supprimer toute dépendance documentaire obligatoire vers PGR pour le développement courant ;
- auditer toutes les crates pour garder `lib.rs` comme façade/documentation/réexports et déplacer les définitions dans des modules possédant une responsabilité claire lorsque cela est justifié ;
- examiner en priorité `knowledge-git-api` et `knowledge-sync-api` ;
- ne pas imposer un générique `api.rs` si des modules `reference`, `revision`, `snapshot`, `repository`, `signal`, etc. expriment mieux l'ownership ;
- ne pas découper artificiellement une petite crate sans bénéfice réel.
Cette tranche sera livrée séparément après `PASS` du présent fix.
## Hors scope du fix
`alpha.3.fix.1` ne doit pas :
- créer `docs/rules/` ou copier des owners PGR détaillés ;
- refactorer maintenant `knowledge-git-api` ou `knowledge-sync-api` en modules supplémentaires ;
- modifier `crates/knowledge-git-api/src/`, `crates/knowledge-git-lib/src/` ou `crates/knowledge-sync-api/` ;
- modifier le comportement Git d'`alpha.3` ;
- introduire `knowledge-app-lib`, scheduler, PostgreSQL ou adapter de forge ;
- modifier `Web/` ;
- préparer `alpha.4` avant `PASS` opérateur du fix.
## Gate opérateur
Exécuter depuis la racine du workspace après application du delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.3.fix.1'
grep -F 'version = "0.0.2-alpha.3.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.3.md | grep -Fx 'FAIL'
git diff --check
python3 - <<'PY'
from pathlib import Path
import re
import tomllib
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
assert len(root["workspace"]["members"]) == 8
plan_path = Path("docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md")
plan = plan_path.read_text(encoding="utf-8")
plan_lines = plan.splitlines()
assert plan_lines[1] == "<!-- version: 7 -->"
trajectory_start = plan_lines.index("## Trajectoire prévisionnelle")
replan_start = plan_lines.index("## Déclencheurs de REPLAN")
trajectory = plan_lines[trajectory_start + 1:replan_start]
for heading in (
"### alpha.3 — refresh, résolution et snapshot réel",
"### alpha.4 — autonomie normative et structure interne des crates",
"### alpha.5 — cas d'usage de synchronisation",
"### beta.1 — réconciliation et revue d'horizon",
"### rc.1 — candidate stable",
"### rel.1 — fermeture stable",
):
assert heading in trajectory, heading
for phrase in (
"Statut opérateur initial : **FAIL**.",
"`v0.1.3` constitue une provenance d'adoption",
"une réévaluation PGR explicitement engagée",
"aucune consultation PGR automatique",
):
assert phrase in plan, phrase
assert "une nouvelle stable PGR modifie un owner" not in plan
rules = Path("RULES.md").read_text(encoding="utf-8")
for phrase in (
"## Provenance PGR et autonomie normative",
"source d'adoption",
"n'est pas une dépendance normative vivante",
"ni un référentiel à charger automatiquement",
"Une future version de PGR n'a aucun effet automatique sur Knowledge",
):
assert phrase in rules, phrase
assert "baseline PGR courante" not in rules
profile = Path("docs/architecture/001-PROJECT_PROFILE.md").read_text(encoding="utf-8")
assert "source d'adoption" in profile
assert "provenance d'adoption figée" in profile
assert "ne consulte pas automatiquement le PGR courant" in profile
git_api_test = Path("crates/knowledge-git-api/unit_tests/lib.rs").read_text(encoding="utf-8")
assert 'assert_eq!(snapshot_result, Ok(crate::GitSnapshot::new(revision, std::path::PathBuf::from("/controlled/snapshot"))));' in git_api_test
integration = Path("crates/knowledge-git-lib/tests/git_read_path.rs").read_text(encoding="utf-8")
assert "//! Integration tests for the real generic Git read path using local temporary repositories." in integration
for phrase in (
"real_local_repository_refreshes_resolves_and_materializes_revision_snapshots",
"invalid_reference_and_dirty_existing_snapshot_are_rejected",
):
assert phrase in integration, phrase
for path in Path(".").rglob("*.md"):
if any(part in {".git", "node_modules", "target", "builds"} for part in path.parts):
continue
lines = path.read_text(encoding="utf-8").splitlines()
assert lines[0] == f"<!-- file: {path.as_posix()} -->", path
assert re.fullmatch(r"<!-- version: [1-9][0-9]* -->", lines[1]), path
print("Knowledge 0.0.2-alpha.3.fix.1 structural/replan audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.3.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-api --all-targets --all-features
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.3.fix.1-metadata.json").read_text(encoding="utf-8"))
packages = {package["name"] for package in metadata["packages"]}
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert packages == expected, packages
assert len(metadata["workspace_members"]) == 8, metadata["workspace_members"]
print("Knowledge 0.0.2-alpha.3.fix.1 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-sync-api)"
test -z "$(git diff --name-only -- crates/knowledge-git-api/src)"
test -z "$(git diff --name-only -- crates/knowledge-git-lib/src)"
test -z "$(git diff --name-only -- prompts)"
test ! -e docs/rules
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Transition
Si la gate est `PASS` :
```text
history alpha.3.fix.1 = PASS
-> commit/push alpha.3.fix.1
-> préparer alpha.4 uniquement ensuite
```
Si la gate est `FAIL`, produire un nouveau fix ciblé ou REPLAN uniquement si le résultat révèle un problème structurel nouveau.

298
deltas/0.0.2/alpha.3.md Normal file
View File

@@ -0,0 +1,298 @@
<!-- file: deltas/0.0.2/alpha.3.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.3 — refresh, résolution et snapshot Git réels
## Base requise
Partir de `0.0.2-alpha.2.fix.2` après application, validation, history `PASS`, commit et push :
```text
deltas/0.0.2/alpha.2.md présent et historique
deltas/0.0.2/alpha.2.fix.1.md présent et historique
deltas/0.0.2/alpha.2.fix.2.md présent et historique
history/0.0.2/alpha.2.md FAIL
history/0.0.2/alpha.2.fix.1.md FAIL
history/0.0.2/alpha.2.fix.2.md PASS
```
Les deltas et histories antérieurs ne doivent pas être réécrits.
La version devient :
```text
0.0.2-alpha.3
```
## Scope
Cette tranche complète la matérialisation Git de lecture déjà prévue par le plan :
- faire implémenter réellement `knowledge-git-api::GitRepository` par `knowledge-git-lib` ;
- enregistrer explicitement les configurations physiques des sources dans `GitBackend` sans les faire remonter dans le domaine ou l'API provider-neutral ;
- ajouter au contrat uniquement `GitSnapshot` et `prepare_snapshot`, maintenant nécessaires ;
- initialiser ou rafraîchir le mirror avec pruning ;
- résoudre une référence opaque vers un commit réel ;
- matérialiser un worktree détaché adressé par la révision résolue ;
- valider un snapshot existant avant réutilisation ;
- prouver le chemin avec de vrais repositories Git temporaires locaux.
Cette tranche ne matérialise pas encore le cas d'usage `Changed` / `Unchanged` d'`alpha.4`.
## Changements apportés
### Contrat provider-neutral
`knowledge-git-api` ajoute `GitSnapshot`, contenant uniquement :
```text
GitRevision
PathBuf de lecture locale
```
`GitRepository` gagne :
```text
prepare_snapshot(source_id, revision)
```
Aucune API de forge, notion de remote, commande Git ou configuration physique n'entre dans le contrat.
### Backend configuré
`GitBackend` conserve sa configuration globale puis enregistre explicitement des `GitSourceConfig` par `SourceId`.
Un doublon est refusé par une erreur récupérable. Une opération provider-neutral sur une source inconnue échoue également sans exposer son URL ou son chemin.
### Refresh
Le refresh :
1. vérifie que la source logique possède une configuration physique enregistrée ;
2. initialise le mirror s'il n'existe pas encore ;
3. exécute ensuite :
```text
git --git-dir <mirror> remote update --prune
```
Aucune API de forge n'est nécessaire.
### Résolution
La résolution exige un mirror existant et utilise :
```text
git --git-dir <mirror> rev-parse --verify --end-of-options <reference>^{commit}
```
La sortie produite par le backend doit être non vide et hexadécimale avant de devenir `GitRevision`. Aucune longueur SHA particulière n'est figée.
### Snapshot
Le chemin de snapshot reste :
```text
<state-root>/snapshots/<source-key>/<resolved-revision>/
```
Un snapshot absent est créé par :
```text
git --git-dir <mirror> worktree add --detach -- <snapshot-path> <resolved-revision>
```
Un snapshot existant n'est réutilisé qu'après :
```text
git -C <snapshot> rev-parse --verify HEAD
git -C <snapshot> status --porcelain=v1 --untracked-files=all
```
Le `HEAD` doit correspondre exactement à la révision demandée et le statut doit être vide. Une divergence ou un snapshot sale est rejeté sans suppression automatique.
### Tests réels
`crates/knowledge-git-lib/tests/git_read_path.rs` crée un repository Git local temporaire réel et vérifie notamment :
- initialisation du mirror via la voie `refresh` ;
- résolution de `refs/heads/main` ;
- création puis réutilisation idempotente d'un snapshot ;
- ajout d'un second commit source, refresh et nouvelle révision ;
- snapshot distinct pour la nouvelle révision ;
- rejet d'une référence inexistante ;
- rejet d'un snapshot existant rendu sale.
Ces tests exercent la frontière `knowledge_git_api::GitRepository` et le client Git réel. Les mocks existants restent uniquement des tests unitaires des arguments/processus.
## Hors scope confirmé
La tranche n'ajoute pas :
- `knowledge-app-lib` ;
- scheduler ;
- persistance de dernière révision ;
- PostgreSQL ou migrations ;
- adapter Gitea/GitHub/GitLab ;
- webhook ;
- status/diff public, branch, commit, merge, rebase ou push ;
- dépendance Git Rust secondaire ;
- changement sous `Web/` ;
- changement de rendu ou de route HTTP.
## Gate opérateur
Exécuter depuis la racine du workspace après application du delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.3'
grep -F 'version = "0.0.2-alpha.3"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.2.fix.2.md | grep -Fx 'PASS'
git diff --check
python3 - <<'PY'
from pathlib import Path
import tomllib
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
manifest = tomllib.loads(Path("crates/knowledge-git-lib/Cargo.toml").read_text(encoding="utf-8"))
assert len(root["workspace"]["members"]) == 8
assert set(manifest["dependencies"]) == {"knowledge-domain-lib", "knowledge-git-api"}
api = Path("crates/knowledge-git-api/src/lib.rs").read_text(encoding="utf-8")
backend = Path("crates/knowledge-git-lib/src/backend.rs").read_text(encoding="utf-8")
error = Path("crates/knowledge-git-lib/src/error.rs").read_text(encoding="utf-8")
integration = Path("crates/knowledge-git-lib/tests/git_read_path.rs").read_text(encoding="utf-8")
for phrase in (
"pub struct GitSnapshot",
"fn prepare_snapshot(",
):
assert phrase in api, phrase
for phrase in (
"impl knowledge_git_api::GitRepository for crate::GitBackend",
"pub fn register_source(",
'std::ffi::OsString::from("remote")',
'std::ffi::OsString::from("update")',
'std::ffi::OsString::from("--prune")',
'std::ffi::OsString::from("rev-parse")',
'std::ffi::OsString::from("--verify")',
'std::ffi::OsString::from("--end-of-options")',
'std::ffi::OsString::from("worktree")',
'std::ffi::OsString::from("add")',
'std::ffi::OsString::from("--detach")',
'std::ffi::OsString::from("status")',
'std::ffi::OsString::from("--porcelain=v1")',
'std::ffi::OsString::from("--untracked-files=all")',
):
assert phrase in backend, phrase
for phrase in (
"DuplicateSource",
"SourceNotConfigured",
"MirrorMissing",
"InvalidRevision",
"InvalidSnapshot",
"RefreshMirror",
"ResolveRevision",
"CreateSnapshot",
"InspectSnapshot",
):
assert phrase in error, phrase
for phrase in (
"real_local_repository_refreshes_resolves_and_materializes_revision_snapshots",
"invalid_reference_and_dirty_existing_snapshot_are_rejected",
"knowledge_git_api::GitRepository::refresh",
"knowledge_git_api::GitRepository::resolve_revision",
"knowledge_git_api::GitRepository::prepare_snapshot",
):
assert phrase in integration, phrase
for forbidden in (
'Command::new("sh")',
'Command::new("bash")',
'Command::new("cmd")',
'Command::new("powershell")',
"knowledge-app-lib",
"knowledge-forge-gitea-lib",
"knowledge-store-postgres-lib",
):
assert forbidden not in backend + error, forbidden
print("Knowledge 0.0.2-alpha.3 structural audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
git --version
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.3-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-api --all-targets --all-features
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.3-metadata.json").read_text(encoding="utf-8"))
packages = {package["name"] for package in metadata["packages"]}
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert packages == expected, packages
assert len(metadata["workspace_members"]) == 8, metadata["workspace_members"]
print("Knowledge 0.0.2-alpha.3 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-sync-api)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
test ! -e history/0.0.2/alpha.3.md
git add -A
git diff --cached --check
git status --short
```
## Résultat attendu
La tranche est `PASS` uniquement si :
- le formatage, la compilation et Clippy strict sont propres ;
- le test `knowledge-git-api` passe ;
- les tests unitaires de `knowledge-git-lib` passent ;
- les deux tests Git réels de `git_read_path` passent ;
- les tests workspace passent ;
- aucun changement hors scope n'est observé ;
- aucun history `alpha.3` n'est créé avant réception des résultats opérateur.
Un `FAIL`, `PARTIAL` ou `BLOCKED` doit être historisé avant tout fix. Un `PASS` autorise uniquement `0.0.2-alpha.4`.

View File

@@ -0,0 +1,171 @@
<!-- file: deltas/0.0.2/alpha.4.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.4.fix.1 — construction interne des types Git API
## Base requise
Partir de `0.0.2-alpha.4` après application de son history `FAIL`, commit et push :
```text
history/0.0.2/alpha.4.md FAIL
```
La version devient :
```text
0.0.2-alpha.4.fix.1
```
Les deltas, histories et prompts historiques ne doivent pas être réécrits.
## Diagnostic
La séparation de `knowledge-git-api/src/lib.rs` en modules propriétaires est correcte, mais deux constructeurs ont conservé la forme antérieure via le réexport crate-root :
```rust
return crate::GitReference(value.to_owned());
return crate::GitRevision(value.to_owned());
```
Les champs tuple sont privés à leurs modules propriétaires. Rust refuse donc ces deux constructions avec `E0423`.
## Scope
Corriger uniquement :
```text
crates/knowledge-git-api/src/reference.rs
crates/knowledge-git-api/src/revision.rs
```
Les constructeurs doivent utiliser leur type propriétaire directement via :
```rust
return Self(value.to_owned());
```
Mettre à jour uniquement les artefacts de version/pilotage nécessaires au fix :
```text
VERSION
Cargo.toml
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
deltas/0.0.2/alpha.4.fix.1.md
```
## Hors scope
Le fix ne doit pas :
- rendre publics les champs tuple de `GitReference` ou `GitRevision` ;
- changer l'API publique crate-root ;
- utiliser `crate::reference::GitReference(...)` ou `crate::revision::GitRevision(...)` pour contourner le contrat de façade ;
- modifier `repository.rs`, `snapshot.rs`, `knowledge-sync-api`, `knowledge-git-lib/src` ou `Web/` ;
- modifier les owners locaux, templates, audits ou le mapping PGR introduits par `alpha.4` ;
- ajouter une fonctionnalité de synchronisation réservée à `alpha.5`.
## Gate opérateur
Exécuter depuis la racine après application du delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.4.fix.1'
grep -F 'version = "0.0.2-alpha.4.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.4.md | grep -Fx 'FAIL'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 - <<'PY'
from pathlib import Path
import tomllib
reference = Path("crates/knowledge-git-api/src/reference.rs").read_text(encoding="utf-8")
revision = Path("crates/knowledge-git-api/src/revision.rs").read_text(encoding="utf-8")
root = Path("crates/knowledge-git-api/src/lib.rs").read_text(encoding="utf-8")
assert "return Self(value.to_owned());" in reference
assert "return Self(value.to_owned());" in revision
assert "return crate::GitReference(value.to_owned());" not in reference
assert "return crate::GitRevision(value.to_owned());" not in revision
assert "crate::reference::GitReference(" not in reference
assert "crate::revision::GitRevision(" not in revision
assert "pub struct GitReference(String);" in reference
assert "pub struct GitRevision(String);" in revision
for module in ("mod reference;", "mod repository;", "mod revision;", "mod snapshot;"):
assert module in root, module
for item in ("GitReference", "GitRepository", "GitRevision", "GitSnapshot"):
assert f"::{item};" in root, item
workspace = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
assert len(workspace["workspace"]["members"]) == 8
print("Knowledge 0.0.2-alpha.4.fix.1 constructor/facade audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.4.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-api --all-targets --all-features
cargo test -p knowledge-sync-api --all-targets --all-features
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.4.fix.1-metadata.json").read_text(encoding="utf-8"))
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert {package["name"] for package in metadata["packages"]} == expected
assert len(metadata["workspace_members"]) == 8
print("Knowledge 0.0.2-alpha.4.fix.1 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-git-lib/src)"
test -z "$(git diff --name-only -- crates/knowledge-sync-api)"
test -z "$(git diff --name-only -- crates/knowledge-git-api/src/lib.rs crates/knowledge-git-api/src/repository.rs crates/knowledge-git-api/src/snapshot.rs)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
test ! -e history/0.0.2/alpha.4.fix.1.md
git add -A
git diff --cached --check
git status --short
```
## Transition attendue
Si toute la gate est `PASS`, produire `history/0.0.2/alpha.4.fix.1.md`, appliquer/committer/pousser ce history puis préparer seulement ensuite `0.0.2-alpha.5`.

283
deltas/0.0.2/alpha.4.md Normal file
View File

@@ -0,0 +1,283 @@
<!-- file: deltas/0.0.2/alpha.4.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.4 — autonomie normative et façades de crates
## Base requise
Partir de `0.0.2-alpha.3.fix.1` après application de son history `PASS`, commit et push :
```text
history/0.0.2/alpha.3.fix.1.md PASS
```
La version devient :
```text
0.0.2-alpha.4
```
Les deltas, histories et prompts historiques ne doivent pas être réécrits.
## Mission
Fermer l'adoption PGR `v0.1.3` comme migration ponctuelle : Knowledge doit désormais posséder localement les règles, patterns et audits réellement nécessaires à son développement quotidien et ne plus devoir charger PGR au début d'une version, d'une session ou d'une tranche.
La même tranche normalise les crate-roots bibliothèque comme façades sans changer les APIs publiques ni la sémantique Git validée en `alpha.3`.
## Scope documentaire et gouvernance
Créer les owners locaux :
```text
docs/rules/000-README.md
docs/rules/200-GOVERNANCE.md
docs/rules/210-WORKFLOW.md
docs/rules/300-DOCUMENTATION.md
docs/rules/400-VALIDATION.md
docs/rules/510-RUST_CARGO.md
docs/rules/530-WEB_FRONTEND.md
```
Créer le mapping de provenance :
```text
docs/architecture/003-PGR_ADOPTION_MAP.md
```
Le mapping sélectionne et adapte uniquement ce qui est utile : gouvernance, workflow, documentation, validation, Rust/Cargo et contrats Web. Les owners locaux doivent être suffisamment complets pour retrouver sans PGR le lifecycle/versioning, les fixes/REPLAN, les contrats delta/history/handoff, les rôles documentaires et les catégories de preuve. Les meta-owners de skeleton/framework restent historiques ou locaux lorsqu'un document séparé n'apporte pas de valeur.
PGR `v0.1.3` reste mentionné uniquement comme source d'adoption. Une future version PGR n'a aucun effet automatique.
## Templates locaux
Créer :
```text
docs/templates/001-NEW_SESSION_PROMPT.md
docs/templates/002-RELEASE_PLAN.md
docs/templates/003-OPERATOR_HANDOFF.md
docs/templates/004-RUST_LIBRARY_CRATE.md
```
Le template de nouvelle session doit partir de la stable Knowledge et ne doit pas demander de rechercher une stable PGR, sauf adoption externe explicitement inscrite au scope.
## Audits localement possédés
Adopter et adapter les audits Markdown génériques nécessaires :
```text
scripts/audit_markdown_headers.py
scripts/audit_markdown_links.py
scripts/audit_markdown_tables.py
```
Ils ignorent les arbres générés usuels, y compris `builds`, `node_modules`, `target` et `.git`.
Ajouter :
```text
scripts/audit_rust_crate_root_facades.py
```
Cet audit read-only vérifie que les `src/lib.rs` des crates bibliothèque ne contiennent pas de définitions top-level de production.
## Structure des crates
`knowledge-git-api` devient :
```text
src/lib.rs
src/reference.rs
src/repository.rs
src/revision.rs
src/snapshot.rs
```
`knowledge-sync-api` devient :
```text
src/lib.rs
src/event_source.rs
src/signal.rs
```
Les noms publics crate-root restent strictement les mêmes. Aucun consommateur ne doit dépendre des modules propriétaires privés.
Les autres crates bibliothèque sont auditées. `knowledge-domain-lib`, `knowledge-git-lib` et `knowledge-web-lib` respectent déjà le contrat et ne sont pas découpées davantage sans besoin réel.
## Hors scope
Cette tranche ne doit pas :
- charger ou adopter une version PGR postérieure à `v0.1.3` ;
- recopier toute l'arborescence PGR ;
- modifier le comportement de `knowledge-git-lib` ;
- changer l'API publique crate-root des contrats Git/sync ;
- modifier `Web/` ;
- ajouter `knowledge-app-lib`, PostgreSQL, forge, auth/admin ou scheduler ;
- implémenter le cas d'usage `Changed` / `Unchanged`, réservé à `alpha.5`.
## Gate opérateur
Exécuter depuis la racine après application du delta :
```bash
test "$(cat VERSION)" = '0.0.2-alpha.4'
grep -F 'version = "0.0.2-alpha.4"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.3.fix.1.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 - <<'PY'
from pathlib import Path
import tomllib
required = (
"docs/rules/200-GOVERNANCE.md",
"docs/rules/210-WORKFLOW.md",
"docs/rules/300-DOCUMENTATION.md",
"docs/rules/400-VALIDATION.md",
"docs/rules/510-RUST_CARGO.md",
"docs/rules/530-WEB_FRONTEND.md",
"docs/architecture/003-PGR_ADOPTION_MAP.md",
"docs/templates/001-NEW_SESSION_PROMPT.md",
"docs/templates/002-RELEASE_PLAN.md",
"docs/templates/003-OPERATOR_HANDOFF.md",
"docs/templates/004-RUST_LIBRARY_CRATE.md",
"scripts/audit_markdown_headers.py",
"scripts/audit_markdown_links.py",
"scripts/audit_markdown_tables.py",
"scripts/audit_rust_crate_root_facades.py",
)
for value in required:
assert Path(value).is_file(), value
rules = Path("RULES.md").read_text(encoding="utf-8")
assert "Owners normatifs locaux" in rules
assert "Une future version de PGR n'a aucun effet automatique sur Knowledge" in rules
assert "baseline PGR courante" not in rules
owner_contracts = {
"docs/rules/200-GOVERNANCE.md": (
"X.Y.Z-alpha.N.fix.M",
"CONTINUE-NEXT-SESSION",
"rel.1 FAIL / PARTIAL / BLOCKED",
),
"docs/rules/210-WORKFLOW.md": (
"sasedev-knowledge-<X.Y.Z>-<tranche>.delta.zip",
"Le delta suivant ne doit pas être appliqué avant matérialisation Git du history précédent.",
"rapport post-tag",
),
"docs/rules/300-DOCUMENTATION.md": (
"`ROADMAP.md` décrit les objectifs",
"Documents vivants et historiques",
"Revue d'horizon",
),
"docs/rules/400-VALIDATION.md": (
"FORMAT",
"PUBLISH",
"Une commande qui échoue dans la gate reste un `FAIL`",
),
}
for path, phrases in owner_contracts.items():
text = Path(path).read_text(encoding="utf-8")
for phrase in phrases:
assert phrase in text, (path, phrase)
mapping = Path("docs/architecture/003-PGR_ADOPTION_MAP.md").read_text(encoding="utf-8")
for phrase in (
"PGR tag v0.1.3",
"Décisions ADOPT",
"Décisions KEEP-LOCAL",
"Décisions DEFER / non applicables",
"Après `PASS` d'`alpha.4`",
):
assert phrase in mapping, phrase
session_template = Path("docs/templates/001-NEW_SESSION_PROMPT.md").read_text(encoding="utf-8")
assert "Une session normale Knowledge ne consulte pas PGR" in session_template
assert "<NEXT_RELEASE>-alpha.1" in session_template
assert "stable PGR courante" not in session_template
git_api_root = Path("crates/knowledge-git-api/src/lib.rs").read_text(encoding="utf-8")
for module in ("mod reference;", "mod repository;", "mod revision;", "mod snapshot;"):
assert module in git_api_root, module
for item in ("GitReference", "GitRepository", "GitRevision", "GitSnapshot"):
assert f"::{item};" in git_api_root, item
sync_root = Path("crates/knowledge-sync-api/src/lib.rs").read_text(encoding="utf-8")
for module in ("mod event_source;", "mod signal;"):
assert module in sync_root, module
for item in ("RepositoryEventSource", "RepositorySyncCause", "RepositorySyncSignal"):
assert item in sync_root, item
root = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
assert len(root["workspace"]["members"]) == 8
print("Knowledge 0.0.2-alpha.4 autonomy/structure audit: clean")
PY
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo fmt --all -- --check
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.4-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-api --all-targets --all-features
cargo test -p knowledge-sync-api --all-targets --all-features
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PY'
import json
from pathlib import Path
metadata = json.loads(Path("/tmp/sasedev-knowledge-0.0.2-alpha.4-metadata.json").read_text(encoding="utf-8"))
expected = {
"knowledge-domain-lib",
"knowledge-git-api",
"knowledge-git-lib",
"knowledge-sync-api",
"knowledge-web-lib",
"knowledge-pgr-server",
"knowledge-howtos-server",
"knowledge-snippets-server",
}
assert {package["name"] for package in metadata["packages"]} == expected
assert len(metadata["workspace_members"]) == 8
print("Knowledge 0.0.2-alpha.4 Cargo metadata audit: clean")
PY
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test -z "$(git diff --name-only -- crates/knowledge-git-lib/src)"
test ! -e crates/knowledge-app-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
test ! -e history/0.0.2/alpha.4.md
git add -A
git diff --cached --check
git status --short
```
## Transition attendue
Si toute la gate est `PASS`, produire `history/0.0.2/alpha.4.md`, appliquer/committer/pousser ce history puis préparer seulement ensuite `0.0.2-alpha.5`.

138
deltas/0.0.2/alpha.5.md Normal file
View File

@@ -0,0 +1,138 @@
<!-- file: deltas/0.0.2/alpha.5.md -->
<!-- version: 1 -->
# Delta 0.0.2-alpha.5 — cas d'usage partagé de synchronisation
## Statut
À appliquer après commit/push du history `0.0.2-alpha.4.fix.1` `PASS`.
## Scope
Cette tranche matérialise `knowledge-app-lib` comme owner de l'orchestration partagée de synchronisation de lecture. Elle compose les contrats Git et sync déjà prouvés sans introduire de stockage, scheduler, forge ou runtime réseau.
Le cas d'usage :
```text
RepositorySyncSignal
-> GitRepository::refresh
-> GitRepository::resolve_revision
-> comparaison previous_revision
-> Unchanged si identique
-> GitRepository::prepare_snapshot si changement
-> Changed { revision, snapshot }
```
La cause du signal n'altère pas le chemin de vérité Git. `Periodic` est prouvée comme une autre origine du même appel, sans scheduler concret.
## Règle de formatage
La gate applique le formatter avant les audits Rust :
```bash
cargo fmt --all
cargo fmt --all -- --check
```
Les audits du code Rust viennent seulement ensuite. Cette règle est maintenant possédée localement par `docs/rules/400-VALIDATION.md` et `docs/rules/510-RUST_CARGO.md`.
## Gate opérateur
```bash
test "$(cat VERSION)" = '0.0.2-alpha.5'
grep -F 'version = "0.0.2-alpha.5"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.4.fix.1.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-alpha.5-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-app-lib --all-targets --all-features
cargo test -p knowledge-git-api --all-targets --all-features
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test -p knowledge-sync-api --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
import json
from pathlib import Path
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-alpha.5-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
manifest = Path('crates/knowledge-app-lib/Cargo.toml').read_text(encoding='utf-8')
assert 'knowledge-git-api.workspace = true' in manifest
assert 'knowledge-sync-api.workspace = true' in manifest
assert '[dev-dependencies]' in manifest
assert 'knowledge-domain-lib.workspace = true' in manifest
source = Path('crates/knowledge-app-lib/src/repository_sync.rs').read_text(encoding='utf-8')
for phrase in (
'RepositorySyncOutcome',
'Unchanged',
'Changed',
'GitRepository::refresh',
'GitRepository::resolve_revision',
'GitRepository::prepare_snapshot',
'previous_revision',
):
assert phrase in source, phrase
for forbidden in ('postgres', 'gitea', 'github', 'gitlab', 'actix', 'tokio', 'scheduler'):
assert forbidden not in source.lower(), forbidden
rule400 = Path('docs/rules/400-VALIDATION.md').read_text(encoding='utf-8')
rule510 = Path('docs/rules/510-RUST_CARGO.md').read_text(encoding='utf-8')
assert rule400.index('cargo fmt --all') < rule400.index('audits Rust structurels applicables')
assert rule510.index('cargo fmt --all') < rule510.index('audit_rust_test_layout.py')
print('Knowledge 0.0.2-alpha.5 sync/format-order audit: clean')
PYGATE
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- Web)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Transition
Un `PASS` autorise uniquement `0.0.2-beta.1`. Un échec requis produit un history factuel et un `alpha.5.fix.N` ou un REPLAN selon la nature du défaut.

View File

@@ -0,0 +1,270 @@
<!-- file: deltas/0.0.2/beta.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-beta.1.fix.1 — clôture de la stabilisation runtime
## Base requise
État `0.0.2-beta.1` committé et poussé après application du history :
```text
history/0.0.2/beta.1.md = PARTIAL
```
La version devient :
```text
0.0.2-beta.1.fix.1
```
## Cause
`beta.1` acquiert la réconciliation, la revue d'horizon, les audits Markdown/Rust, Cargo/Clippy/tests, la baseline Web et le manifest Vite. La gate générée n'exécute toutefois pas les trois smokes HTTP requis par le plan pendant la stabilisation.
Cette omission appartient au contrat de validation livré, pas à une défaillance de l'opérateur. Le history `beta.1` reste donc `PARTIAL`.
## Objet du fix
Le fix n'introduit aucune fonctionnalité. Il :
- rend explicite chez `400-VALIDATION` qu'un smoke de stabilisation exigé par le plan ne peut pas être omis au motif qu'il n'existe aucun diff runtime ;
- documente `beta.1 = PARTIAL` dans le plan et la matrice ;
- rejoue la gate beta complète ;
- ajoute les trois smokes HTTP production manquants avant d'autoriser la RC.
## Scope
Fichiers remplacés :
```text
VERSION
Cargo.toml
docs/rules/400-VALIDATION.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
Fichier ajouté :
```text
deltas/0.0.2/beta.1.fix.1.md
```
Aucun fichier sous `crates/` ou `Web/` n'est remplacé. Aucun package, dépendance, contrat métier, comportement Git ou prochaine release n'est ajouté.
## Gate opérateur
```bash
set -euo pipefail
test "$(cat VERSION)" = '0.0.2-beta.1.fix.1'
grep -F 'version = "0.0.2-beta.1.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/beta.1.md | grep -Fx 'PARTIAL'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-beta.1.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
python3 - <<'PYGATE'
import json
from pathlib import Path
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-beta.1.fix.1-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
manifest_path = Path('../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json')
assert manifest_path.is_file(), manifest_path
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
assert 'src/main.ts' in manifest, manifest.keys()
study = Path('docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md').read_text(encoding='utf-8')
assert 'Aucun prompt `0.0.3` n'est créé' in study
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '0.0.3' not in roadmap
assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists()
assert not Path('history/0.0.2/beta.1.fix.1.md').exists()
rule400 = Path('docs/rules/400-VALIDATION.md').read_text(encoding='utf-8')
assert 'Lorsqu'un plan exige un smoke de stabilisation, cette preuve est obligatoire' in rule400
print('Knowledge 0.0.2-beta.1.fix.1 stabilization audit: clean')
PYGATE
python3 - <<'PYSMOKE'
import json
import os
from pathlib import Path
import signal
import subprocess
import time
import urllib.request
root = Path.cwd()
build_root = (root / "../builds/sasedev-knowledge").resolve()
manifest_path = build_root / "web/template-lab/.vite/manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
entry = manifest["src/main.ts"]
asset_paths = ["/" + entry["file"].lstrip("/")]
asset_paths.extend("/" + value.lstrip("/") for value in entry.get("css", []))
subprocess.run([
"cargo", "build",
"-p", "knowledge-pgr-server",
"-p", "knowledge-howtos-server",
"-p", "knowledge-snippets-server",
], check=True)
servers = (
("knowledge-pgr-server", "KNOWLEDGE_PGR_BIND_ADDRESS", 18181),
("knowledge-howtos-server", "KNOWLEDGE_HOWTOS_BIND_ADDRESS", 18182),
("knowledge-snippets-server", "KNOWLEDGE_SNIPPETS_BIND_ADDRESS", 18183),
)
for binary_name, bind_variable, port in servers:
binary = build_root / "target/debug" / binary_name
assert binary.is_file(), binary
env = os.environ.copy()
env.pop("KNOWLEDGE_VITE_DEV_ORIGIN", None)
env["KNOWLEDGE_VITE_MANIFEST_PATH"] = str(manifest_path)
env[bind_variable] = f"127.0.0.1:{port}"
process = subprocess.Popen(
[str(binary)],
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
start_new_session=True,
text=True,
)
try:
health_url = f"http://127.0.0.1:{port}/healthz"
for attempt in range(50):
try:
with urllib.request.urlopen(health_url, timeout=1) as response:
body = response.read().decode("utf-8")
assert response.status == 200
assert body == "ok"
break
except Exception:
if process.poll() is not None:
stdout, stderr = process.communicate()
raise AssertionError((binary_name, process.returncode, stdout, stderr))
if attempt == 49:
raise
time.sleep(0.1)
with urllib.request.urlopen(f"http://127.0.0.1:{port}/", timeout=2) as response:
html = response.read().decode("utf-8")
assert response.status == 200
for asset_path in asset_paths:
assert asset_path in html, (binary_name, asset_path)
with urllib.request.urlopen(f"http://127.0.0.1:{port}{asset_path}", timeout=2) as response:
assert response.status == 200, (binary_name, asset_path, response.status)
print(f"{binary_name} smoke PASS")
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
print("Knowledge 0.0.2-beta.1.fix.1 HTTP smoke: clean")
PYSMOKE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge beta.1.fix.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- crates Web)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Résultat attendu
Le `PASS` doit notamment afficher :
```text
Knowledge 0.0.2-beta.1.fix.1 stabilization audit: clean
knowledge-pgr-server smoke PASS
knowledge-howtos-server smoke PASS
knowledge-snippets-server smoke PASS
Knowledge 0.0.2-beta.1.fix.1 HTTP smoke: clean
```
Les warnings npm/Sass déjà observés restent non bloquants si les commandes terminent avec succès.
## Transition
`PASS` autorise uniquement :
```text
0.0.2-rc.1
```
Le history `beta.1.fix.1` est produit seulement après réception de la gate opérateur complète.

View File

@@ -0,0 +1,161 @@
<!-- file: deltas/0.0.2/beta.1.fix.2.md -->
<!-- version: 1 -->
# Delta 0.0.2-beta.1.fix.2 — correction finale de la gate beta
## Base requise
État `0.0.2-beta.1.fix.1` committé et poussé après application du history :
```text
history/0.0.2/beta.1.fix.1.md = FAIL
```
La version devient :
```text
0.0.2-beta.1.fix.2
```
## Cause
`beta.1.fix.1` acquiert toutes les preuves runtime manquantes, y compris les trois smokes HTTP, mais sa gate contient deux défauts propres au livrable de validation :
- l'audit Python de stabilisation utilise une chaîne délimitée par apostrophes contenant `n'est`, produisant `SyntaxError: unterminated string literal` ;
- `docs/validation/001-VALIDATION_MATRIX.md` se termine par une ligne vide supplémentaire, ce que `git diff --check` puis `git diff --cached --check` rejettent comme `new blank line at EOF`.
Le script d'hygiène texte de `fix.1` vérifie seulement `endswith(b'\n')` et ne détecte donc pas le suffixe invalide `b'\n\n'`.
## Objet du fix
Le fix n'introduit aucune fonctionnalité et ne rejoue pas mécaniquement les preuves runtime déjà acquises sur un arbre produit inchangé. Il :
- corrige le quoting de l'audit Python ;
- normalise tous ses fichiers remplacés à exactement un LF terminal ;
- rend durable la règle d'un unique LF terminal chez `400-VALIDATION` ;
- étend l'audit d'hygiène pour rejeter `\n\n` à EOF ;
- interdit dans les gates destinées au shell interactif les modifications persistantes `set -e`, `set -u` et `set -o pipefail` ;
- conserve comme preuves cumulatives les résultats `PASS` de `beta.1.fix.1` pour Cargo/Clippy/tests, Web et smokes HTTP.
## Scope
Fichiers remplacés :
```text
VERSION
Cargo.toml
docs/rules/400-VALIDATION.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
Fichier ajouté :
```text
deltas/0.0.2/beta.1.fix.2.md
```
Aucun fichier sous `crates/` ou `Web/` n'est remplacé. Aucun package, dépendance, contrat métier, comportement Git ou future release n'est ajouté.
## Gate opérateur
Exécuter les commandes ci-dessous directement dans le shell interactif. La gate ne modifie volontairement pas les options globales du shell.
```bash
test "$(cat VERSION)" = '0.0.2-beta.1.fix.2'
grep -F 'version = "0.0.2-beta.1.fix.2"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/beta.1.fix.1.md | grep -Fx 'FAIL'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 - <<'PYGATE'
from pathlib import Path
import tomllib
workspace = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
assert len(workspace["workspace"]["members"]) == 9
study = Path("docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md").read_text(encoding="utf-8")
assert "Aucun prompt `0.0.3` n'est créé" in study
roadmap = Path("ROADMAP.md").read_text(encoding="utf-8")
assert "0.0.3" not in roadmap
assert not Path("prompts/003-V0_0_3_START_PROMPT.md").exists()
assert not Path("history/0.0.2/beta.1.fix.2.md").exists()
previous = Path("history/0.0.2/beta.1.fix.1.md").read_text(encoding="utf-8")
for phrase in (
"knowledge-pgr-server smoke PASS",
"knowledge-howtos-server smoke PASS",
"knowledge-snippets-server smoke PASS",
"cargo check --workspace",
"cargo test --workspace --all-targets --all-features",
):
assert phrase in previous, phrase
rule400 = Path("docs/rules/400-VALIDATION.md").read_text(encoding="utf-8")
assert "exactement un unique LF" in rule400
assert "ne doit pas modifier durablement les options du shell" in rule400
print("Knowledge 0.0.2-beta.1.fix.2 stabilization audit: clean")
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge beta.1.fix.2 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- crates Web)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
## Résultat attendu
Le `PASS` doit notamment afficher :
```text
Knowledge 0.0.2-beta.1.fix.2 stabilization audit: clean
Knowledge beta.1.fix.2 text hygiene: clean (... file(s))
```
Aucune commande Cargo, npm ou smoke n'est répétée dans ce fix documentaire : leurs preuves `PASS` sont conservées dans le history `beta.1.fix.1`, et le contrôle de scope prouve l'absence de diff sous `crates/` et `Web/`.
## Transition
`PASS` ferme la stabilisation beta et autorise uniquement :
```text
0.0.2-rc.1
```
Le history `beta.1.fix.2` est produit seulement après réception de la gate opérateur complète.

168
deltas/0.0.2/beta.1.md Normal file
View File

@@ -0,0 +1,168 @@
<!-- file: deltas/0.0.2/beta.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-beta.1 — réconciliation Git/synchronisation et revue d'horizon
## Base requise
État `0.0.2-alpha.5` committé et poussé après application de son history `PASS`.
La version devient :
```text
0.0.2-beta.1
```
## Nature de la tranche
`beta.1` est une tranche de stabilisation et de réconciliation. Elle n'ajoute aucune grande fonctionnalité, aucune crate, aucune dépendance, aucune route et aucun comportement Git.
Elle réconcilie :
- les neuf packages réellement matérialisés ;
- les contrats Git/synchronisation réellement obtenus ;
- les `DEFER` et hors-scope ;
- les références historiques ambiguës aux alphas `0.0.1` ;
- le changelog candidat ;
- la revue d'horizon.
L'étude durable est :
```text
docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md
```
## Revue d'horizon
Aucune release postérieure à `0.0.2` n'est engagée ni numérotée. Les domaines futurs restent non numérotés jusqu'à décision d'une mission explicite. Aucun prompt `0.0.3` n'est créé.
## Gate opérateur
```bash
test "$(cat VERSION)" = '0.0.2-beta.1'
grep -F 'version = "0.0.2-beta.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/alpha.5.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-beta.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
python3 - <<'PYGATE'
import json
from pathlib import Path
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-beta.1-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
study = Path('docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md').read_text(encoding='utf-8')
for phrase in (
'knowledge-git-lib backend process Git contrôlé',
'knowledge-app-lib cas d\'usage partagé Changed / Unchanged',
'DEFER confirmés',
'Aucun prompt `0.0.3` n\'est créé',
):
assert phrase in study, phrase
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert 'aucune release postérieure à `0.0.2` n\'est engagée ni numérotée' in roadmap
assert '0.0.3' not in roadmap
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
assert '## 0.0.2 (candidate)' in changelog
for forbidden in ('alpha.1.fix', 'alpha.2.fix', 'alpha.3.fix', 'alpha.4.fix'):
assert forbidden not in changelog, forbidden
architecture = Path('docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md').read_text(encoding='utf-8')
assert '`0.0.1-alpha.4` possède le frontend indépendant' in architecture
assert '`0.0.1-alpha.5` matérialise `knowledge-web-lib`' in architecture
assert '## Réconciliation `0.0.2-beta.1`' in architecture
manifest_path = Path('../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json')
assert manifest_path.is_file(), manifest_path
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
assert 'src/main.ts' in manifest, manifest.keys()
assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists()
assert not Path('history/0.0.2/beta.1.md').exists()
print('Knowledge 0.0.2-beta.1 reconciliation/horizon audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge beta.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- crates Web)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
Les warnings de dépendances frontend déjà connus ne constituent pas à eux seuls un `FAIL` si les commandes npm/Vite terminent avec succès.
## Transition
Un `PASS` autorise uniquement `0.0.2-rc.1`. Un échec requis produit un history factuel et un `beta.1.fix.N` ou un REPLAN selon la nature du défaut.

319
deltas/0.0.2/rc.1.md Normal file
View File

@@ -0,0 +1,319 @@
<!-- file: deltas/0.0.2/rc.1.md -->
<!-- version: 1 -->
# Delta 0.0.2-rc.1 — release candidate
## Base requise
État `0.0.2-beta.1.fix.2` committé et poussé après application du history :
```text
history/0.0.2/beta.1.fix.2.md = PASS
```
La version devient :
```text
0.0.2-rc.1
```
## Objet
Geler le scope de `0.0.2` et exécuter sa gate complète de release candidate.
Aucune nouvelle fonctionnalité, dépendance, crate, route, comportement Git/sync ou décision d'horizon n'est introduite.
## Scope
La RC remplace uniquement :
```text
VERSION
Cargo.toml
README.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
et ajoute :
```text
deltas/0.0.2/rc.1.md
```
Aucun fichier n'est supprimé. `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, les études, l'architecture et les owners locaux sont gelés.
## Histories attendus avant RC
La gate doit retrouver exactement les treize histories antérieurs suivants :
```text
alpha.1 FAIL
alpha.1.fix.1 PASS
alpha.2 FAIL
alpha.2.fix.1 FAIL
alpha.2.fix.2 PASS
alpha.3 FAIL
alpha.3.fix.1 PASS
alpha.4 FAIL
alpha.4.fix.1 PASS
alpha.5 PASS
beta.1 PARTIAL
beta.1.fix.1 FAIL
beta.1.fix.2 PASS
```
Pendant la gate, `rc.1.md` doit être le seul delta `0.0.2` sans history correspondant.
## Présentation candidate
La RC conserve :
```text
CHANGELOG.md -> ## 0.0.2 (candidate)
ROADMAP.md -> 0.0.2 encore engagé, aucune release suivante numérotée
aucun prompt -> 0.0.3
```
La présentation stable appartient à `rel.1`, pas à la RC.
## Gate opérateur
Exécuter directement dans le shell interactif. Ne pas activer `set -e`, `set -u` ou `set -o pipefail` globalement.
```bash
test "$(cat VERSION)" = '0.0.2-rc.1'
grep -F 'version = "0.0.2-rc.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/beta.1.fix.2.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-rc.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
python3 - <<'PYGATE'
import json
from pathlib import Path
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-rc.1-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
statuses = {
'alpha.1': 'FAIL',
'alpha.1.fix.1': 'PASS',
'alpha.2': 'FAIL',
'alpha.2.fix.1': 'FAIL',
'alpha.2.fix.2': 'PASS',
'alpha.3': 'FAIL',
'alpha.3.fix.1': 'PASS',
'alpha.4': 'FAIL',
'alpha.4.fix.1': 'PASS',
'alpha.5': 'PASS',
'beta.1': 'PARTIAL',
'beta.1.fix.1': 'FAIL',
'beta.1.fix.2': 'PASS',
}
for tranche, status in statuses.items():
path = Path(f'history/0.0.2/{tranche}.md')
assert path.is_file(), path
text = path.read_text(encoding='utf-8')
assert f'## Statut\n\n{status}' in text, (tranche, status)
history_names = {path.stem for path in Path('history/0.0.2').glob('*.md')}
assert history_names == set(statuses), history_names
delta_names = {path.stem for path in Path('deltas/0.0.2').glob('*.md')}
assert delta_names == set(statuses) | {'rc.1'}, delta_names
assert not Path('history/0.0.2/rc.1.md').exists()
assert '## 0.0.2 (candidate)' in Path('CHANGELOG.md').read_text(encoding='utf-8')
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '( ) 0.0.2' in roadmap
assert '0.0.3' not in roadmap
assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists()
for forbidden in (
Path('crates/knowledge-forge-gitea-lib'),
Path('crates/knowledge-store-postgres-lib'),
):
assert not forbidden.exists(), forbidden
print('Knowledge 0.0.2-rc.1 release-candidate audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.2-rc.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
python3 - <<'PYMANIFEST'
import json
from pathlib import Path
manifest_path = Path('../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json')
assert manifest_path.is_file(), manifest_path
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
assert 'src/main.ts' in manifest, manifest.keys()
entry = manifest['src/main.ts']
assert entry.get('file'), entry
assert entry.get('css'), entry
print('Knowledge 0.0.2-rc.1 Vite manifest audit: clean')
PYMANIFEST
python3 - <<'PYSMOKE'
import json
import os
from pathlib import Path
import signal
import subprocess
import time
import urllib.request
root = Path.cwd()
build_root = (root / '../builds/sasedev-knowledge').resolve()
manifest_path = build_root / 'web/template-lab/.vite/manifest.json'
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
entry = manifest['src/main.ts']
asset_paths = ['/' + entry['file'].lstrip('/')]
asset_paths.extend('/' + value.lstrip('/') for value in entry.get('css', []))
subprocess.run([
'cargo', 'build',
'-p', 'knowledge-pgr-server',
'-p', 'knowledge-howtos-server',
'-p', 'knowledge-snippets-server',
], check=True)
servers = (
('knowledge-pgr-server', 'KNOWLEDGE_PGR_BIND_ADDRESS', 18181),
('knowledge-howtos-server', 'KNOWLEDGE_HOWTOS_BIND_ADDRESS', 18182),
('knowledge-snippets-server', 'KNOWLEDGE_SNIPPETS_BIND_ADDRESS', 18183),
)
for binary_name, bind_variable, port in servers:
binary = build_root / 'target/debug' / binary_name
assert binary.is_file(), binary
env = os.environ.copy()
env.pop('KNOWLEDGE_VITE_DEV_ORIGIN', None)
env['KNOWLEDGE_VITE_MANIFEST_PATH'] = str(manifest_path)
env[bind_variable] = f'127.0.0.1:{port}'
process = subprocess.Popen(
[str(binary)], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True, text=True,
)
try:
health_url = f'http://127.0.0.1:{port}/healthz'
for attempt in range(50):
try:
with urllib.request.urlopen(health_url, timeout=1) as response:
body = response.read().decode('utf-8')
assert response.status == 200
assert body == 'ok'
break
except Exception:
if process.poll() is not None:
stdout, stderr = process.communicate()
raise AssertionError((binary_name, process.returncode, stdout, stderr))
if attempt == 49:
raise
time.sleep(0.1)
with urllib.request.urlopen(f'http://127.0.0.1:{port}/', timeout=2) as response:
html = response.read().decode('utf-8')
assert response.status == 200
for asset_path in asset_paths:
assert asset_path in html, (binary_name, asset_path)
with urllib.request.urlopen(f'http://127.0.0.1:{port}{asset_path}', timeout=2) as response:
assert response.status == 200, (binary_name, asset_path, response.status)
print(f'{binary_name} smoke PASS')
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
print('Knowledge 0.0.2-rc.1 HTTP smoke: clean')
PYSMOKE
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- crates Web CHANGELOG.md ROADMAP.md docs/architecture docs/studies docs/rules prompts)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
```
Les warnings de dépendances frontend déjà connus ne constituent pas à eux seuls un `FAIL` si npm/Vite terminent avec succès.
## Transition
Un `PASS` de `rc.1` autorise uniquement `rel.1`.
Un défaut fonctionnel important découvert en RC impose un retour de phase ou un REPLAN. Un défaut limité à la gate, au packaging ou aux métadonnées de candidate peut produire `rc.1.fix.1` selon le diagnostic réel.
Ne pas committer `rc.1` avant production de son history à partir des résultats opérateur.

335
deltas/0.0.2/rel.1.md Normal file
View File

@@ -0,0 +1,335 @@
<!-- file: deltas/0.0.2/rel.1.md -->
<!-- version: 1 -->
# Delta 0.0.2 rel.1 — promotion stable pré-tag
## Baseline et version
Baseline : `0.0.2-rc.1`, avec `history/0.0.2/rc.1.md` appliqué et committé.
Ce delta MUST NOT être appliqué avant ce commit.
Version avant application :
```text
0.0.2-rc.1
```
Version après application :
```text
0.0.2
```
`rel.1` identifie la tranche de clôture opérationnelle ; il ne fait pas partie de la version SemVer du produit.
## Objet
Promouvoir mécaniquement la candidate validée vers la stable `0.0.2` sans rouvrir le scope.
La tranche finalise uniquement la version stable, la présentation stable du README, la roadmap, le changelog, l'index documentaire, le plan `0.0.2` et la matrice de validation.
Aucun fichier Rust, frontend, dépendance fonctionnelle, contrat Git/sync, règle normative, étude ou prompt de reprise n'est modifié.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
README.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.2/rel.1.md
```
DELETE : aucun.
Le history `rc.1` est livré séparément. Le history `rel.1` sera produit uniquement après la gate opérateur.
## Présentation stable attendue
Après application :
```text
VERSION 0.0.2
Cargo workspace version 0.0.2
ROADMAP (x) 0.0.2
CHANGELOG ## 0.0.2
README 0.0.2 présentée comme stable
plan 002 plan historique de la stable 0.0.2
aucun prompt 0.0.3 oui
```
Aucune release postérieure à `0.0.2` n'est engagée ni numérotée.
## Gate pré-tag
Exécuter depuis la racine du dépôt :
```bash
test "$(cat VERSION)" = '0.0.2'
grep -F 'version = "0.0.2"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/rc.1.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-rel.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
python3 - <<'PYGATE'
import json
from pathlib import Path
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-rel.1-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
statuses = {
'alpha.1': 'FAIL',
'alpha.1.fix.1': 'PASS',
'alpha.2': 'FAIL',
'alpha.2.fix.1': 'FAIL',
'alpha.2.fix.2': 'PASS',
'alpha.3': 'FAIL',
'alpha.3.fix.1': 'PASS',
'alpha.4': 'FAIL',
'alpha.4.fix.1': 'PASS',
'alpha.5': 'PASS',
'beta.1': 'PARTIAL',
'beta.1.fix.1': 'FAIL',
'beta.1.fix.2': 'PASS',
'rc.1': 'PASS',
}
for tranche, status in statuses.items():
path = Path(f'history/0.0.2/{tranche}.md')
assert path.is_file(), path
text = path.read_text(encoding='utf-8')
assert f'## Statut\n\n{status}' in text, (tranche, status)
history_names = {path.stem for path in Path('history/0.0.2').glob('*.md')}
assert history_names == set(statuses), history_names
delta_names = {path.stem for path in Path('deltas/0.0.2').glob('*.md')}
assert delta_names == set(statuses) | {'rel.1'}, delta_names
assert not Path('history/0.0.2/rel.1.md').exists()
assert '## 0.0.2\n' in Path('CHANGELOG.md').read_text(encoding='utf-8')
assert '## 0.0.2 (candidate)' not in Path('CHANGELOG.md').read_text(encoding='utf-8')
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '- `(x) 0.0.2`' in roadmap
assert '0.0.3' not in roadmap
readme = Path('README.md').read_text(encoding='utf-8')
assert '`0.0.2` est la stable du chemin Git générique de lecture' in readme
index = Path('docs/000-README.md').read_text(encoding='utf-8')
assert 'plan historique de la stable Git générique' in index
plan = Path('docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md').read_text(encoding='utf-8')
assert 'Plan historique de la stable `0.0.2`' in plan
assert '`rc.1` est `PASS`' in plan
assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists()
for forbidden in (
Path('crates/knowledge-forge-gitea-lib'),
Path('crates/knowledge-store-postgres-lib'),
):
assert not forbidden.exists(), forbidden
print('Knowledge 0.0.2-rel.1 stable audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.2-rel.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
python3 - <<'PYMANIFEST'
import json
from pathlib import Path
manifest_path = Path('../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json')
assert manifest_path.is_file(), manifest_path
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
assert 'src/main.ts' in manifest, manifest.keys()
entry = manifest['src/main.ts']
assert entry.get('file'), entry
assert entry.get('css'), entry
print('Knowledge 0.0.2-rel.1 Vite manifest audit: clean')
PYMANIFEST
python3 - <<'PYSMOKE'
import json
import os
from pathlib import Path
import signal
import subprocess
import time
import urllib.request
root = Path.cwd()
build_root = (root / '../builds/sasedev-knowledge').resolve()
manifest_path = build_root / 'web/template-lab/.vite/manifest.json'
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
entry = manifest['src/main.ts']
asset_paths = ['/' + entry['file'].lstrip('/')]
asset_paths.extend('/' + value.lstrip('/') for value in entry.get('css', []))
subprocess.run([
'cargo', 'build',
'-p', 'knowledge-pgr-server',
'-p', 'knowledge-howtos-server',
'-p', 'knowledge-snippets-server',
], check=True)
servers = (
('knowledge-pgr-server', 'KNOWLEDGE_PGR_BIND_ADDRESS', 18181),
('knowledge-howtos-server', 'KNOWLEDGE_HOWTOS_BIND_ADDRESS', 18182),
('knowledge-snippets-server', 'KNOWLEDGE_SNIPPETS_BIND_ADDRESS', 18183),
)
for binary_name, bind_variable, port in servers:
binary = build_root / 'target/debug' / binary_name
assert binary.is_file(), binary
env = os.environ.copy()
env.pop('KNOWLEDGE_VITE_DEV_ORIGIN', None)
env['KNOWLEDGE_VITE_MANIFEST_PATH'] = str(manifest_path)
env[bind_variable] = f'127.0.0.1:{port}'
process = subprocess.Popen(
[str(binary)], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True, text=True,
)
try:
health_url = f'http://127.0.0.1:{port}/healthz'
for attempt in range(50):
try:
with urllib.request.urlopen(health_url, timeout=1) as response:
body = response.read().decode('utf-8')
assert response.status == 200
assert body == 'ok'
break
except Exception:
if process.poll() is not None:
stdout, stderr = process.communicate()
raise AssertionError((binary_name, process.returncode, stdout, stderr))
if attempt == 49:
raise
time.sleep(0.1)
with urllib.request.urlopen(f'http://127.0.0.1:{port}/', timeout=2) as response:
html = response.read().decode('utf-8')
assert response.status == 200
for asset_path in asset_paths:
assert asset_path in html, (binary_name, asset_path)
with urllib.request.urlopen(f'http://127.0.0.1:{port}{asset_path}', timeout=2) as response:
assert response.status == 200, (binary_name, asset_path, response.status)
print(f'{binary_name} smoke PASS')
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
print('Knowledge 0.0.2-rel.1 HTTP smoke: clean')
PYSMOKE
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -z "$(git diff --name-only -- crates Web docs/architecture docs/studies docs/rules prompts)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
test ! -e history/0.0.2/rel.1.md
git add -A
git diff --cached --check
git status --short
```
## Transition
### PASS
Produire séparément `history/0.0.2/rel.1.md`, l'appliquer, puis créer le commit stable avec le message exact :
```text
0.0.2
```
Après push de `master`, créer et publier le tag annoté `v0.0.2`, vérifier l'objet tag local/distant et son commit déréférencé, puis télécharger l'archive source réellement produite depuis le tag et produire le rapport externe :
```text
sasedev-knowledge-0.0.2-publish.001-report.md
```
Aucune prochaine release n'est engagée à ce stade. Un futur prompt n'est créé qu'après décision explicite de sa mission et s'appuie sur la stable publiée/vérifiée `0.0.2`.
### FAIL
Créer `history/0.0.2/rel.1.md` avec le diagnostic réel, ne pas créer le tag et corriger uniquement la fermeture dans `rel.1.fix.1`.

View File

@@ -0,0 +1,184 @@
<!-- file: deltas/0.0.3/alpha.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.1.fix.1 — cycle documentaire et horizon
## Baseline requise
Partir de `0.0.3-alpha.1` après application et commit/push de son history `PARTIAL`.
La version devient :
```text
0.0.3-alpha.1.fix.1
```
## Objet
Corriger le cadrage documentaire signalé par l'opérateur sans modifier la mission Git ni le code produit :
- retirer l'entrée `0.0.3 (candidate)` écrite trop tôt dans `CHANGELOG.md` ;
- codifier que le changelog de release est préparé vers la stabilisation à partir des changements réellement acquis ;
- conserver `prompts/003-V0_0_3_START_PROMPT.md` comme rattrapage valide, mais codifier que le prompt de `N+1` est normalement produit pendant les dernières phases de `N` après revue d'horizon ;
- transformer `ROADMAP.md` en vue de la release courante plus un horizon prévisionnel borné et explicitement révisable ;
- établir l'horizon actuel `0.0.4` à `0.0.7` sans les considérer comme des missions engagées ;
- mettre à jour le plan `0.0.3` afin que `beta.1` réévalue cet horizon et prépare le prochain prompt avant la stable si la mission suivante est suffisamment définie.
Aucun fichier sous `crates/` ou `Web/` n'est modifié.
## Horizon introduit
L'horizon actuel est :
```text
0.0.4 PostgreSQL partagé et migrations
0.0.5 contenu structuré et lecture publique
0.0.6 identité / autorisation / administration locale
0.0.7 propositions / revue / publication
```
Le statut `(h)` signifie horizon prévisionnel : numéro, ordre et scope peuvent être révisés lors des revues d'horizon ultérieures. Seule `0.0.3` est engagée aujourd'hui.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
CHANGELOG.md
ROADMAP.md
docs/rules/210-WORKFLOW.md
docs/rules/300-DOCUMENTATION.md
docs/templates/001-NEW_SESSION_PROMPT.md
docs/templates/002-RELEASE_PLAN.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
ADD :
```text
deltas/0.0.3/alpha.1.fix.1.md
```
DELETE : aucun.
`prompts/003-V0_0_3_START_PROMPT.md` reste inchangé : il constitue le rattrapage nécessaire à l'ouverture de `0.0.3`.
Aucun history `alpha.1.fix.1` n'est inclus dans ce delta.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.1.fix.1'
grep -F 'version = "0.0.3-alpha.1.fix.1"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.1.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
from pathlib import Path
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
assert '## 0.0.3' not in changelog
assert '## 0.0.2' in changelog
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '- `( ) 0.0.3`' in roadmap
for version in ('0.0.4', '0.0.5', '0.0.6', '0.0.7'):
assert f'- `(h) {version}`' in roadmap, version
assert 'horizon prévisionnel' in roadmap
workflow = Path('docs/rules/210-WORKFLOW.md').read_text(encoding='utf-8')
assert 'pendant les dernières phases de `N`' in workflow
assert 'rattrapage et non le cycle normal' in workflow
documentation = Path('docs/rules/300-DOCUMENTATION.md').read_text(encoding='utf-8')
assert "Il n'est normalement pas rédigé au démarrage" in documentation
assert 'au moins les deux releases d\'horizon qui la suivent' in documentation
prompt_template = Path('docs/templates/001-NEW_SESSION_PROMPT.md').read_text(encoding='utf-8')
assert 'pendant les dernières phases de la release `N`' in prompt_template
assert 'MUST NOT inventer ces valeurs' in prompt_template
assert Path('prompts/003-V0_0_3_START_PROMPT.md').is_file()
assert not Path('history/0.0.3/alpha.1.fix.1.md').exists()
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.1.fix.1 documentation lifecycle audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.1.fix.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates Web)"
test ! -e history/0.0.3/alpha.1.fix.1.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier Web ni contrat runtime n'est modifié.
## Transition
### PASS
Produire `history/0.0.3/alpha.1.fix.1.md`, l'appliquer, commit/push `0.0.3-alpha.1.fix.1`, puis préparer `0.0.3-alpha.2`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une correction bornée utilise `alpha.1.fix.2`; un problème qui invalide la trajectoire appelle un REPLAN.

View File

@@ -0,0 +1,173 @@
<!-- file: deltas/0.0.3/alpha.1.fix.2.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.1.fix.2 — complétion de la preuve opérateur
## Baseline requise
Partir de `0.0.3-alpha.1.fix.1` après application et commit/push de son history `PARTIAL`.
La version devient :
```text
0.0.3-alpha.1.fix.2
```
## Objet
Fermer uniquement la preuve restée incomplète sur `alpha.1.fix.1`.
Aucune décision fonctionnelle n'est modifiée et aucune fonctionnalité Git n'est ajoutée. Le fix :
- conserve le résultat `PARTIAL` de `alpha.1.fix.1` comme artefact historique ;
- met à jour le plan actif pour refléter cette transition ;
- rejoue la non-régression nécessaire après le bump de version ;
- exécute explicitement les assertions documentaires/horizon, l'hygiène texte, l'invariance du code produit et la politique des lockfiles ;
- termine par le staging requis.
Le développement du contrat d'écriture provider-neutral reste réservé à `alpha.2` après `PASS` de ce fix.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
ADD :
```text
deltas/0.0.3/alpha.1.fix.2.md
```
DELETE : aucun.
Aucun fichier sous `crates/` ou `Web/` n'est modifié.
Aucun history `alpha.1.fix.2` n'est inclus dans ce delta.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.1.fix.2'
grep -F 'version = "0.0.3-alpha.1.fix.2"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.1.fix.2-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
from pathlib import Path
history = Path('history/0.0.3/alpha.1.fix.1.md').read_text(encoding='utf-8')
assert '\nPARTIAL\n' in history
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
assert '## 0.0.3' not in changelog
assert '## 0.0.2' in changelog
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '- `( ) 0.0.3`' in roadmap
for version in ('0.0.4', '0.0.5', '0.0.6', '0.0.7'):
assert f'- `(h) {version}`' in roadmap, version
assert 'horizon prévisionnel' in roadmap
workflow = Path('docs/rules/210-WORKFLOW.md').read_text(encoding='utf-8')
assert 'pendant les dernières phases de `N`' in workflow
assert 'rattrapage et non le cycle normal' in workflow
documentation = Path('docs/rules/300-DOCUMENTATION.md').read_text(encoding='utf-8')
assert "Il n'est normalement pas rédigé au démarrage" in documentation
assert "au moins les deux releases d'horizon qui la suivent" in documentation
prompt_template = Path('docs/templates/001-NEW_SESSION_PROMPT.md').read_text(encoding='utf-8')
assert 'pendant les dernières phases de la release `N`' in prompt_template
assert 'MUST NOT inventer ces valeurs' in prompt_template
plan = Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').read_text(encoding='utf-8')
assert '### alpha.1.fix.2 — complétion de la preuve opérateur' in plan
assert 'Un `PASS` complet de `alpha.1.fix.2` autorise `0.0.3-alpha.2`' in plan
assert Path('prompts/003-V0_0_3_START_PROMPT.md').is_file()
assert not Path('history/0.0.3/alpha.1.fix.2.md').exists()
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.1.fix.2 completion audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.1.fix.2 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates Web)"
test ! -e history/0.0.3/alpha.1.fix.2.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier Web ni contrat runtime n'est modifié.
## Transition
### PASS
Produire `history/0.0.3/alpha.1.fix.2.md`, l'appliquer, commit/push `0.0.3-alpha.1.fix.2`, puis préparer `0.0.3-alpha.2`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une correction bornée utilise `alpha.1.fix.3`; `.fix.3` déclenche en outre la revue `REPLAN` prévue par le plan actif.

288
deltas/0.0.3/alpha.1.md Normal file
View File

@@ -0,0 +1,288 @@
<!-- file: deltas/0.0.3/alpha.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.1 — cadrage de l'écriture Git contrôlée
## Baseline requise
Partir de la stable autoritaire :
```text
tag Knowledge v0.0.2
commit Knowledge 183d354a67bec6d2d512c4898f5189759a6120b0
archive SHA-256 0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce
publish report sasedev-knowledge-0.0.2-publish.001-report.md = PASS
```
La stable `0.0.2` ne contenait volontairement aucun prompt `0.0.3`, car sa revue d'horizon n'avait engagé aucune prochaine mission. La présente session a ouvert explicitement une nouvelle mission depuis cette baseline ; le prompt versionné correspondant est donc matérialisé par cette tranche :
```text
prompts/003-V0_0_3_START_PROMPT.md
```
PGR `v0.1.3` reste une provenance historique. Aucune version PGR ultérieure n'est consultée ou adoptée.
La version devient :
```text
0.0.3-alpha.1
```
## Objet
Fermer le cadrage de la prochaine release avant toute modification fonctionnelle :
- vérifier la stable `v0.0.2`, ses règles/owners, architecture, deltas/histories et `DEFER` ;
- sélectionner l'écriture Git générique contrôlée comme mission `0.0.3` ;
- conserver le processus `git` contrôlé déjà validé en `0.0.2` ;
- choisir un worktree d'écriture isolé pour créer une révision candidate ;
- décider que le contrat d'écriture restera distinct du contrat `GitRepository` de lecture ;
- borner la mise à jour distante aux branches `refs/heads/...` avec attente explicite sur leur état précédent ;
- garder une divergence comme conflit récupérable sans rebase/merge automatique ;
- établir l'étude, le plan, le prompt local et la gate de release.
Aucun code produit, aucune crate, aucune dépendance Cargo/npm et aucun fichier Web ne sont ajoutés ou modifiés dans cette tranche.
## Décision d'horizon
La prochaine release engagée est :
```text
0.0.3 — écriture Git contrôlée et push gardé
```
Les domaines PostgreSQL, proposition/revue, rendu/publication produit, recherche, Gitea, auth/admin et opérations restent non numérotés.
`0.0.3` ne ferme pas le workflow de proposition complet. Elle fournit la primitive Git générique dont ce workflow aura besoin : base explicite, mutations confinées, commit candidat et mise à jour distante gardée.
## Contrats retenus
L'étude :
```text
docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md
```
retient :
- un worktree d'écriture isolé sous la racine d'état contrôlée ;
- des mutations de fichiers par chemins relatifs validés ;
- une identité auteur/committer explicite ;
- hooks et prompts neutralisés ;
- une nouvelle révision candidate avant tout push ;
- un push de branche avec comparaison de l'état distant attendu ;
- conflit explicite si la branche a divergé ;
- aucun `push --force` non gardé ;
- aucun rebase/merge automatique ;
- aucune forge obligatoire.
Le plan actif est :
```text
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
avec trajectoire prévisionnelle `alpha.1` à `alpha.4`, puis `beta.1`, `rc.1` et `rel.1`.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
README.md
RULES.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/architecture/001-PROJECT_PROFILE.md
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
prompts/003-V0_0_3_START_PROMPT.md
deltas/0.0.3/alpha.1.md
```
DELETE : aucun.
Aucun fichier sous `crates/` ou `Web/` n'est modifié.
Aucun history `0.0.3-alpha.1` n'est inclus : il doit refléter la gate opérateur réelle.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.1'
grep -F 'version = "0.0.3-alpha.1"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
import json
from pathlib import Path
assert Path('docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md').is_file()
assert Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').is_file()
assert Path('prompts/003-V0_0_3_START_PROMPT.md').is_file()
assert Path('deltas/0.0.3/alpha.1.md').is_file()
assert not Path('history/0.0.3/alpha.1.md').exists()
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '- `( ) 0.0.3`' in roadmap
assert '0.0.4' not in roadmap
rules = Path('RULES.md').read_text(encoding='utf-8')
for phrase in (
'Écriture Git contrôlée',
'MUST viser une branche `refs/heads/...`',
'MUST NOT effectuer un `push --force` non gardé',
):
assert phrase in rules, phrase
study = Path('docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md').read_text(encoding='utf-8')
for phrase in (
'worktree d\'écriture isolé',
'comparaison-et-échange',
'aucun rebase ou merge automatique',
'PGR `v0.1.3` reste une provenance historique',
):
assert phrase in study, phrase
plan = Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').read_text(encoding='utf-8')
headings = (
'## Statut et sources',
'## Mission',
'## État acquis et questions ouvertes',
'## Scope',
'## Hors scope',
'## Stratégie de validation',
'## Trajectoire prévisionnelle',
'## Déclencheurs de REPLAN',
'## Clôture et transition',
'## Revue d\'horizon',
)
positions = [plan.index(value) for value in headings]
assert positions == sorted(positions)
for tranche in ('alpha.1', 'alpha.2', 'alpha.3', 'alpha.4', 'beta.1', 'rc.1', 'rel.1'):
assert f'### {tranche} —' in plan, tranche
prompt = Path('prompts/003-V0_0_3_START_PROMPT.md').read_text(encoding='utf-8')
assert 'Ne pas rechercher, charger ni adopter automatiquement une nouvelle version de PGR.' in prompt
assert '0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce' in prompt
metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.3-alpha.1-metadata.json').read_text(encoding='utf-8'))
expected = {
'knowledge-app-lib',
'knowledge-domain-lib',
'knowledge-git-api',
'knowledge-git-lib',
'knowledge-sync-api',
'knowledge-web-lib',
'knowledge-pgr-server',
'knowledge-howtos-server',
'knowledge-snippets-server',
}
assert {package['name'] for package in metadata['packages']} == expected
assert len(metadata['workspace_members']) == 9
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.1 framing audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates Web)"
test ! -e history/0.0.3/alpha.1.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP dans `alpha.1` : aucun contrat Web ni fichier `Web/` n'est modifié, et la stable `0.0.2` a déjà acquis une gate Web/runtime complète. Cette omission est conforme à la validation proportionnée locale.
## Transition
### PASS
Produire séparément :
```text
history/0.0.3/alpha.1.md
```
avec les résultats réellement observés. Après application du history, commit/push `0.0.3-alpha.1`, puis seulement préparer `0.0.3-alpha.2`.
### FAIL / PARTIAL / BLOCKED
Produire le history réel avec son statut, l'appliquer et le committer avant toute correction.
Une correction bornée à ce cadrage utilise :
```text
0.0.3-alpha.1.fix.1
```
Si le diagnostic invalide la mission, l'ownership ou la stratégie d'écriture, exécuter un REPLAN plutôt que d'absorber une nouvelle responsabilité dans un fix.

232
deltas/0.0.3/alpha.2.md Normal file
View File

@@ -0,0 +1,232 @@
<!-- file: deltas/0.0.3/alpha.2.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.2 — contrat d'écriture Git provider-neutral
## Baseline requise
Partir de `0.0.3-alpha.1.fix.2` après application, commit et push de son history `PASS`.
La version devient :
```text
0.0.3-alpha.2
```
## Objet
Matérialiser uniquement la frontière publique provider-neutral nécessaire aux futures opérations d'écriture Git, sans implémenter encore le worktree d'écriture ou le push distant réel.
La tranche :
- conserve `GitRepository` comme contrat de lecture inchangé ;
- ajoute `GitRepositoryWriter` comme capacité distincte afin de ne pas imposer l'écriture aux consommateurs/fakes lecture seule ;
- introduit `GitMutationPath` pour des chemins relatifs normalisés, sans `.` / `..`, séparateur plateforme, chemin absolu, drive Windows ou composant `.git` ;
- introduit `GitFileMutation` pour écrire/créer/remplacer ou supprimer un fichier ;
- introduit `GitCommitIdentity` et exige une identité explicite ;
- introduit `GitBranch` et borne la publication à une référence complète valide sous `refs/heads/...` ;
- introduit `GitRemoteState` pour représenter explicitement branche absente ou révision attendue ;
- introduit `GitWriteRequest`, `GitPushRequest` et `GitPushOutcome` pour séparer création de candidate, mise à jour distante gardée et conflit ;
- rejette une requête sans mutation, un message vide/NUL et plusieurs mutations visant le même chemin ;
- documente la matérialisation de la frontière sans déplacer les responsabilités backend prévues pour `alpha.3` / `alpha.4`.
Aucun processus Git d'écriture n'est lancé par cette tranche.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
README.md
crates/knowledge-git-api/src/lib.rs
crates/knowledge-git-api/unit_tests/lib.rs
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
crates/knowledge-git-api/src/write.rs
deltas/0.0.3/alpha.2.md
```
DELETE : aucun.
Aucun fichier sous `knowledge-git-lib`, `knowledge-app-lib` ou `Web/` n'est modifié.
Aucun history `alpha.2` n'est inclus dans ce delta.
## Contrat public matérialisé
Le contrat expose les valeurs suivantes depuis la façade `knowledge_git_api` :
```text
GitWriteInputError
GitMutationPath
GitFileMutation
GitCommitIdentity
GitBranch
GitRemoteState
GitWriteRequest
GitPushRequest
GitPushOutcome
GitRepositoryWriter
```
Le chemin conceptuel reste :
```text
GitRevision de base
+ mutations confinées
+ identité/message explicites
-> create_candidate
-> GitRevision candidate
GitBranch
+ GitRemoteState attendu
+ GitRevision candidate
-> push_candidate
-> Updated ou Conflict
```
`alpha.2` définit ce contrat mais ne prétend pas encore prouver le confinement filesystem face aux symlinks, la neutralisation des hooks, la création effective d'un commit ou le compare-and-exchange distant. Ces preuves appartiennent au backend `knowledge-git-lib` des tranches suivantes.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.2'
grep -F 'version = "0.0.3-alpha.2"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.2-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-api --all-targets --all-features
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
from pathlib import Path
api = Path('crates/knowledge-git-api/src/lib.rs').read_text(encoding='utf-8')
write = Path('crates/knowledge-git-api/src/write.rs').read_text(encoding='utf-8')
read_contract = Path('crates/knowledge-git-api/src/repository.rs').read_text(encoding='utf-8')
manifest = Path('crates/knowledge-git-api/Cargo.toml').read_text(encoding='utf-8')
plan = Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').read_text(encoding='utf-8')
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
for item in (
'GitWriteInputError',
'GitMutationPath',
'GitFileMutation',
'GitCommitIdentity',
'GitBranch',
'GitRemoteState',
'GitWriteRequest',
'GitPushRequest',
'GitPushOutcome',
'GitRepositoryWriter',
):
assert item in api, item
assert item in write, item
assert 'fn refresh(' in read_contract
assert 'fn resolve_revision(' in read_contract
assert 'fn prepare_snapshot(' in read_contract
assert 'create_candidate' not in read_contract
assert 'push_candidate' not in read_contract
assert 'refs/heads/' in write
assert 'ProtectedGitPath' in write
assert 'DuplicateMutationPath' in write
assert 'GitRemoteState::Absent' not in write or 'GitRemoteState' in write
assert 'rebase' not in write.lower()
assert 'merge' not in write.lower()
for forbidden in ('actix', 'tokio', 'gitea', 'gitlab', 'github', 'postgres', 'sqlx', 'diesel'):
assert forbidden not in manifest.lower(), forbidden
assert '`alpha.1.fix.2` a complété la gate documentaire et technique en `PASS`' in plan
assert '### alpha.2 — contrat d\'écriture provider-neutral' in plan
assert '## 0.0.3' not in changelog
assert Path('history/0.0.3/alpha.1.fix.2.md').is_file()
assert not Path('history/0.0.3/alpha.2.md').exists()
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.2 write-contract audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.2 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates/knowledge-git-lib crates/knowledge-app-lib Web)"
test ! -e history/0.0.3/alpha.2.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : `Web/`, les launchers et leur contrat runtime ne sont pas modifiés.
## Transition
### PASS
Produire `history/0.0.3/alpha.2.md`, l'appliquer, commit/push `0.0.3-alpha.2`, puis préparer `0.0.3-alpha.3` pour le worktree d'écriture isolé et le commit candidat réel dans `knowledge-git-lib`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une correction bornée utilise `alpha.2.fix.N` ; une modification de la mission ou de la frontière d'ownership exige un `REPLAN` explicite plutôt qu'un fix absorbant une nouvelle fonctionnalité.

View File

@@ -0,0 +1,173 @@
<!-- file: deltas/0.0.3/alpha.3.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.3.fix.1 — correction du test de commit candidat
## Baseline requise
Partir de `0.0.3-alpha.3` après application, commit et push de son history `FAIL`.
La version devient :
```text
0.0.3-alpha.3.fix.1
```
## Objet
Corriger uniquement le défaut de compilation observé dans `crates/knowledge-git-lib/tests/git_write_candidate.rs` pendant la gate `alpha.3`.
L'assertion fautive comparait directement :
```text
Result<String, std::io::Error>
```
à un `Ok(String)`. Cette forme exige `PartialEq` sur `std::io::Error`, qui n'est pas implémenté.
Le fix remplace cette comparaison par une vérification de motif sur `snapshot_content.as_deref()` afin de valider uniquement le cas `Ok("first\n")` sans comparer la variante d'erreur.
Aucune logique de `knowledge-git-lib` n'est modifiée. Le worktree, les mutations, le commit candidat, la rétention de référence, le nettoyage et les contrats publics restent byte-for-byte identiques à `alpha.3`.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
crates/knowledge-git-lib/tests/git_write_candidate.rs
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
ADD :
```text
deltas/0.0.3/alpha.3.fix.1.md
```
DELETE : aucun.
`crates/knowledge-git-lib/src/`, `crates/knowledge-git-lib/unit_tests/`, `knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` et `ROADMAP.md` ne sont pas modifiés.
Aucun history `alpha.3.fix.1` n'est inclus dans ce delta.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.3.fix.1'
grep -F 'version = "0.0.3-alpha.3.fix.1"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.3.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
from pathlib import Path
history = Path('history/0.0.3/alpha.3.md').read_text(encoding='utf-8')
assert '\nFAIL\n' in history
test_source = Path('crates/knowledge-git-lib/tests/git_write_candidate.rs').read_text(encoding='utf-8')
assert 'assert!(matches!(snapshot_content.as_deref(), Ok("first\\n")));' in test_source
assert 'assert_eq!(snapshot_content, Ok(String::from("first\\n")));' not in test_source
plan = Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').read_text(encoding='utf-8')
assert "### alpha.3.fix.1 — correction de l'assertion du test candidat" in plan
assert '`alpha.3` est conservée `FAIL`' in plan
assert 'Un `PASS` complet de ce fix autorise `alpha.4`' in plan
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
assert '## 0.0.3' not in changelog
assert not Path('history/0.0.3/alpha.3.fix.1.md').exists()
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.3.fix.1 compile-fix audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [
Path('README.md'),
Path('RULES.md'),
Path('ROADMAP.md'),
Path('CHANGELOG.md'),
Path('docs'),
Path('prompts'),
Path('deltas'),
Path('history'),
]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.3.fix.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates/knowledge-git-lib/src crates/knowledge-git-lib/unit_tests crates/knowledge-git-api crates/knowledge-app-lib Web CHANGELOG.md ROADMAP.md)"
test ! -e history/0.0.3/alpha.3.fix.1.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier Web ni contrat runtime n'est modifié.
## Transition
### PASS
Produire `history/0.0.3/alpha.3.fix.1.md`, l'appliquer, commit/push `0.0.3-alpha.3.fix.1`, puis préparer `0.0.3-alpha.4`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une nouvelle correction bornée utilise `alpha.3.fix.2`; si le diagnostic invalide les invariants du worktree ou du candidat, effectuer un REPLAN avant de poursuivre.

256
deltas/0.0.3/alpha.3.md Normal file
View File

@@ -0,0 +1,256 @@
<!-- file: deltas/0.0.3/alpha.3.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.3 — worktree d'écriture et commit candidat
## Baseline requise
Partir de `0.0.3-alpha.2` après application, commit et push de son history `PASS`.
La version devient :
```text
0.0.3-alpha.3
```
## Objet
Implémenter dans `knowledge-git-lib` la création locale réelle d'une révision candidate à partir d'une base Git explicite, sans mettre encore à jour de branche distante.
La tranche :
- ajoute un worktree d'écriture temporaire et déterministe sous la racine d'état du backend ;
- réutilise le mirror contrôlé du `SourceId` et exige une `GitRevision` de base sûre ;
- applique exactement les mutations `Write` / `Delete` demandées ;
- refuse toute cible ou tout parent filesystem qui est un symlink ;
- stage uniquement les chemins demandés puis vérifie que l'index contient exactement cet ensemble ;
- neutralise la configuration Git système/globale ambiante pertinente et conserve `core.hooksPath` sous contrôle ;
- crée le commit avec auteur et committer explicitement issus de `GitCommitIdentity`, `--no-verify` et `--no-gpg-sign` ;
- vérifie que le candidat a exactement la base demandée comme premier parent ;
- retient l'objet candidat sous `refs/knowledge/candidates/<revision>` avant suppression du worktree ;
- retire le worktree possédé après succès et tente une suppression forcée après une erreur survenue dans ce worktree ;
- refuse un worktree `candidate` préexistant au lieu de le supprimer silencieusement ;
- ajoute des tests d'intégration Git réels pour le candidat et l'échappement par symlink.
`alpha.3` ne met à jour aucun remote et n'implémente pas encore `GitRepositoryWriter` sur `GitBackend`, car ce trait exige également `push_candidate`. L'implémentation complète du trait et le compare-and-exchange distant restent réservés à `alpha.4`.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
README.md
crates/knowledge-git-lib/src/backend.rs
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/unit_tests/backend.rs
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
crates/knowledge-git-lib/src/write.rs
crates/knowledge-git-lib/tests/git_write_candidate.rs
deltas/0.0.3/alpha.3.md
```
DELETE : aucun.
`knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` et `ROADMAP.md` ne sont pas modifiés.
Aucun history `alpha.3` n'est inclus dans ce delta.
## Contrat backend matérialisé
Le chemin local devient :
```text
GitRevision de base
-> mirror contrôlé
-> write-worktrees/<source-key>/candidate
-> mutations confinées
-> git add limité aux chemins demandés
-> commit avec identité explicite
-> vérification HEAD / HEAD^1
-> refs/knowledge/candidates/<revision>
-> suppression du worktree temporaire
-> GitRevision candidate
```
La racine d'état contrôlée contient désormais également :
```text
empty-global.gitconfig
write-worktrees/
```
`empty-global.gitconfig` neutralise la configuration utilisateur globale pour les commandes possédées par le backend ; `GIT_CONFIG_NOSYSTEM=1` neutralise la configuration système. La configuration repository nécessaire au mirror reste disponible, tandis que `core.hooksPath` est forcé vers `hooks-disabled`.
## Confinement filesystem
`GitMutationPath` fournit déjà le confinement lexical. `alpha.3` ajoute le contrôle physique : avant toute écriture ou suppression, chaque composant existant du chemin sous le worktree est inspecté avec `symlink_metadata`.
Un symlink rencontré produit `UnsafeMutationTarget`. Une suppression d'une cible absente produit `MutationTargetMissing`. Les erreurs filesystem restent récupérables et ne contiennent pas le chemin ou le contenu sensible dans leur texte public.
Un worktree préexistant à l'emplacement contrôlé produit `WriteWorktreeExists`. Il n'est pas supprimé automatiquement.
## Candidate et nettoyage
Une candidate n'est valide que si :
- les chemins réellement stagés correspondent exactement aux mutations demandées ;
- le commit est créé avec l'identité explicite de la requête ;
- `HEAD` est une révision Git sûre ;
- `HEAD^1` est exactement la base demandée ;
- une référence interne `refs/knowledge/candidates/<revision>` conserve l'objet avant retrait du worktree.
Le retrait normal n'utilise pas `--force`. Si une erreur survient après création du worktree possédé par l'opération, le backend utilise `worktree remove --force` pour retirer uniquement cet état temporaire connu. Un échec de nettoyage devient `WriteCleanupFailed` afin qu'un état inattendu ne soit pas masqué.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.3'
grep -F 'version = "0.0.3-alpha.3"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.3-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test --workspace --all-targets --all-features
python3 - <<'PYGATE'
from pathlib import Path
history = Path('history/0.0.3/alpha.2.md').read_text(encoding='utf-8')
assert '\nPASS\n' in history
write = Path('crates/knowledge-git-lib/src/write.rs').read_text(encoding='utf-8')
backend = Path('crates/knowledge-git-lib/src/backend.rs').read_text(encoding='utf-8')
errors = Path('crates/knowledge-git-lib/src/error.rs').read_text(encoding='utf-8')
api = Path('crates/knowledge-git-api/src/write.rs').read_text(encoding='utf-8')
plan = Path('docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md').read_text(encoding='utf-8')
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
for required in (
'pub fn create_candidate(',
'symlink_metadata',
'CandidateMutationMismatch',
'GIT_AUTHOR_NAME',
'GIT_COMMITTER_NAME',
'--no-verify',
'--no-gpg-sign',
'HEAD^1',
'refs/knowledge/candidates/',
'RemoveWriteWorktree',
):
assert required in write, required
assert 'GIT_CONFIG_GLOBAL' in backend
assert 'GIT_CONFIG_NOSYSTEM' in backend
assert 'core.hooksPath' in backend
assert 'write_worktrees_path' in backend
assert 'empty_global_config_path' in backend
for required_error in (
'WriteWorktreeExists',
'UnsafeMutationTarget',
'MutationTargetMissing',
'MutationFilesystemFailed',
'CandidateMutationMismatch',
'InvalidCandidate',
'WriteCleanupFailed',
):
assert required_error in errors, required_error
assert 'trait GitRepositoryWriter' in api
assert 'fn create_candidate(' in api
assert 'fn push_candidate(' in api
assert 'impl knowledge_git_api::GitRepositoryWriter for crate::GitBackend' not in write
assert 'GitPushRequest' not in write
assert 'push_candidate' not in write
assert '`alpha.2` a ensuite matérialisé le contrat d\'écriture provider-neutral et sa gate opérateur est `PASS`' in plan
assert '## 0.0.3' not in changelog
assert not Path('history/0.0.3/alpha.3.md').exists()
assert not Path('crates/knowledge-forge-gitea-lib').exists()
assert not Path('crates/knowledge-store-postgres-lib').exists()
assert not Path('migrations').exists()
print('Knowledge 0.0.3-alpha.3 candidate-worktree audit: clean')
PYGATE
python3 - <<'PYTEXT'
from pathlib import Path
roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')]
paths = []
for root in roots:
if root.is_file():
paths.append(root)
elif root.is_dir():
paths.extend(path for path in root.rglob('*') if path.is_file())
for path in paths:
data = path.read_bytes()
assert b'\r' not in data, path
assert data.endswith(b'\n'), path
assert not data.endswith(b'\n\n'), path
text = data.decode('utf-8')
for number, line in enumerate(text.splitlines(), start=1):
assert line == line.rstrip(' \t'), (path, number)
print(f'Knowledge 0.0.3-alpha.3 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
test -z "$(git diff --name-only -- crates/knowledge-git-api crates/knowledge-app-lib Web CHANGELOG.md ROADMAP.md)"
test ! -e history/0.0.3/alpha.3.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier `Web/`, launcher ou contrat runtime Web n'est modifié.
## Transition
### PASS
Produire `history/0.0.3/alpha.3.md`, l'appliquer, commit/push `0.0.3-alpha.3`, puis préparer `0.0.3-alpha.4` pour l'observation distante, le compare-and-exchange et le push gardé avec conflits explicites.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une correction bornée utilise `alpha.3.fix.N` ; une remise en cause du cycle de vie des worktrees, de la frontière provider-neutral ou de la stratégie process Git exige un `REPLAN` explicite.

186
deltas/0.0.3/alpha.4.md Normal file
View File

@@ -0,0 +1,186 @@
<!-- file: deltas/0.0.3/alpha.4.md -->
<!-- version: 1 -->
# Delta 0.0.3-alpha.4 — push gardé et concurrence
## Baseline requise
Partir de `0.0.3-alpha.3.fix.1` après application, commit et push de son history `PASS`.
La version devient :
```text
0.0.3-alpha.4
```
## Objet
Fermer le scope fonctionnel alpha de `0.0.3` en implémentant le push Git provider-neutral gardé par l'état distant attendu.
La tranche :
- conserve la création locale de candidat acquise en `alpha.3.fix.1` ;
- exige que le candidat soit encore retenu sous `refs/knowledge/candidates/<revision>` ;
- observe la branche distante par `git ls-remote --refs origin <refs/heads/...>` ;
- compare l'état observé à `GitRemoteState::Absent` ou `GitRemoteState::Revision(...)` ;
- utilise `--force-with-lease=<branche>:<attendu>` comme compare-and-exchange autoritaire ;
- neutralise `remote.origin.mirror=true` uniquement pour la commande de push explicite du mirror ;
- crée une branche uniquement lorsque l'absence est explicitement attendue ;
- réobserve l'état distant après un rejet de push afin de produire un conflit récupérable en cas de divergence ;
- implémente `knowledge_git_api::GitRepositoryWriter` sur `GitBackend` ;
- ajoute des tests d'intégration avec remote bare local pour mise à jour, création et conflit sans écrasement.
Aucun rebase/merge automatique, aucun force-push non gardé, aucune API de forge, aucun PostgreSQL et aucune orchestration applicative de proposition ne sont introduits.
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
README.md
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/src/write.rs
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
crates/knowledge-git-lib/tests/git_write_push.rs
deltas/0.0.3/alpha.4.md
```
DELETE : aucun.
`knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` et `ROADMAP.md` ne sont pas modifiés.
Aucun history `alpha.4` n'est inclus dans ce delta.
## Compare-and-exchange distant
Le chemin d'écriture complet devient :
```text
base explicite
-> candidat local retenu
-> observation distante
-> comparaison à l'état attendu
-> push --force-with-lease exact
-> Updated(candidate)
ou Conflict { actual }
```
Pour une branche existante :
```text
--force-with-lease=refs/heads/main:<revision-attendue>
```
Pour une branche qui doit être absente :
```text
--force-with-lease=refs/heads/proposal:
```
L'observation préalable fournit un conflit rapide, mais elle n'est pas la garde de concurrence définitive. La lease est évaluée par Git au moment du push et protège la course entre observation et mise à jour distante.
Un mirror créé avec `git clone --mirror` possède `remote.origin.mirror=true`. Le push de `alpha.4` fournit donc explicitement :
```text
-c remote.origin.mirror=false
```
pour cette commande uniquement, afin d'autoriser le refspec ciblé sans transformer le mirror ou pousser toutes ses références internes.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta :
```bash
test "$(cat VERSION)" = '0.0.3-alpha.4'
grep -F 'version = "0.0.3-alpha.4"' Cargo.toml
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-alpha.4-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test -p knowledge-git-lib --test git_write_push
cargo test --workspace --all-targets --all-features
```
Les contrôles spécifiques sont volontairement courts et indépendants afin d'éviter les problèmes de copier/coller de blocs heredoc :
```bash
grep -F 'PASS' history/0.0.3/alpha.3.fix.1.md
grep -F 'pub fn push_candidate(' crates/knowledge-git-lib/src/write.rs
grep -F -- '--force-with-lease=' crates/knowledge-git-lib/src/write.rs
grep -F 'remote.origin.mirror=false' crates/knowledge-git-lib/src/write.rs
grep -F 'GitOperation::InspectRemote' crates/knowledge-git-lib/src/write.rs
grep -F 'GitOperation::PushCandidate' crates/knowledge-git-lib/src/write.rs
grep -F 'impl knowledge_git_api::GitRepositoryWriter for crate::GitBackend' crates/knowledge-git-lib/src/write.rs
grep -F 'guarded_push_updates_expected_branch_and_creates_only_expected_absent_branch' crates/knowledge-git-lib/tests/git_write_push.rs
grep -F 'guarded_push_returns_conflict_after_concurrent_remote_change_without_overwrite' crates/knowledge-git-lib/tests/git_write_push.rs
! grep -F '## 0.0.3' CHANGELOG.md
test ! -e history/0.0.3/alpha.4.md
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
test ! -e migrations
test -z "$(git diff --name-only -- crates/knowledge-git-api crates/knowledge-app-lib Web CHANGELOG.md ROADMAP.md)"
```
Hygiène texte :
```bash
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("docs"),Path("prompts"),Path("deltas"),Path("history")]; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file()]; bad=[str(p) for p in paths if (lambda b: b"\r" in b or not b.endswith(b"\n") or b.endswith(b"\n\n") or any(line != line.rstrip(" \t") for line in b.decode("utf-8").splitlines()))(p.read_bytes())]; print(f"Knowledge 0.0.3-alpha.4 text hygiene: clean ({len(paths)} file(s))") if not bad else (_ for _ in ()).throw(AssertionError(bad))'
```
Politique des lockfiles et staging :
```bash
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then echo 'Cargo.lock must remain untracked for Knowledge' >&2; exit 1; fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier `Web/`, launcher ou contrat runtime Web n'est modifié.
## Transition
### PASS
Produire `history/0.0.3/alpha.4.md`, l'appliquer, commit/push `0.0.3-alpha.4`, puis préparer `0.0.3-beta.1` pour réconciliation, revue d'horizon étendue et stabilisation.
La revue d'horizon beta doit notamment réintroduire explicitement la trajectoire produit complète des trois sites PGR / Snippets / Howtos : templates publics et admin, prototypes Web, Actix Web, adaptation Maud, PostgreSQL, consultation/gestion et workflow de proposition/publication.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Une correction bornée utilise `alpha.4.fix.N` ; une remise en cause de la garantie compare-and-exchange, de la frontière provider-neutral ou du modèle de remote exige un `REPLAN` explicite.

207
deltas/0.0.3/beta.1.md Normal file
View File

@@ -0,0 +1,207 @@
<!-- file: deltas/0.0.3/beta.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-beta.1 — réconciliation, changelog candidat et horizon produit
## Baseline requise
Partir de `0.0.3-alpha.4` après application, commit et push de son history :
```text
history/0.0.3/alpha.4.md = PASS
```
La version devient :
```text
0.0.3-beta.1
```
## Nature de la tranche
`beta.1` est une tranche de stabilisation documentaire et de réconciliation. Elle n'ajoute aucun comportement Rust/Web, aucune crate, aucune dépendance et aucune migration.
Elle :
- réconcilie le chemin Git de lecture/écriture réellement obtenu ;
- prépare le changelog `0.0.3 (candidate)` à partir des capacités validées ;
- reclassifie la persistance PostgreSQL comme prochaine mission `0.0.4` ;
- étend l'horizon afin de représenter les trois produits PGR/HOWTOs/snippets et leurs phases de templates publics/admin puis d'intégration Actix/Maud ;
- prépare le prompt `0.0.4` pendant la fin de `0.0.3`, conformément au cycle normal ;
- conserve les autres `DEFER` sans implémentation anticipée.
L'étude durable est :
```text
docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
```
## Scope des fichiers
REPLACE :
```text
VERSION
Cargo.toml
README.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/architecture/001-PROJECT_PROFILE.md
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
prompts/004-V0_0_4_START_PROMPT.md
deltas/0.0.3/beta.1.md
```
DELETE : aucun.
`crates/` et `Web/` ne sont pas modifiés. Aucun history `beta.1` n'est inclus dans ce delta.
## Horizon retenu
La prochaine mission potentielle est :
```text
0.0.4 fondation PostgreSQL partagée et migrations
```
L'horizon révisable devient ensuite :
```text
0.0.5 modèles de contenu et projections PGR/HOWTO/snippet
0.0.6 templates publics des trois sites, pages de garde incluses
0.0.7 templates d'administration des trois sites
0.0.8 routes publiques Actix Web + adaptation Maud
0.0.9 auth/autorisation + routes admin Actix Web + adaptation Maud
0.0.10 gestion/proposition/revue/publication Git + PostgreSQL
```
Cette numérotation donne une trajectoire produit lisible mais ne transforme pas `0.0.5+` en engagements irrévocables. Les revues d'horizon futures peuvent encore réordonner, scinder ou fusionner ces étapes.
## Handoff 0.0.4
`prompts/004-V0_0_4_START_PROMPT.md` est créé maintenant parce que la mission PostgreSQL est suffisamment décidée.
Le prompt ne fabrique aucune preuve postérieure à `0.0.3` : il ne contient ni commit stable réel de `v0.0.3`, ni SHA-256 d'archive taggée, ni statut post-tag supposé. La future session devra recevoir l'archive et le rapport réels après publication et les vérifier avant développement.
## Gate opérateur
Exécuter depuis la racine du dépôt après application du delta.
### Version, documentation et Rust
```bash
test "$(cat VERSION)" = '0.0.3-beta.1'
grep -F 'version = "0.0.3-beta.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.3/alpha.4.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-beta.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test -p knowledge-git-lib --test git_write_push
cargo test --workspace --all-targets --all-features
```
### Baseline Web de stabilisation
```bash
(cd Web/knowledge-template-lab && npm ci)
(cd Web/knowledge-template-lab && npm run typecheck)
(cd Web/knowledge-template-lab && npm run build)
```
Audit du manifest, sous forme d'une commande indépendante :
```bash
python3 -c 'import json; from pathlib import Path; p=Path("../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json"); m=json.loads(p.read_text(encoding="utf-8")); e=m["src/main.ts"]; assert e.get("file"); assert e.get("css"); print("Knowledge 0.0.3-beta.1 Vite manifest audit: clean")'
```
Les smokes HTTP complets des trois launchers seront obligatoires en `rc.1`. `beta.1` ne modifie aucun runtime Web et valide ici la baseline npm/typecheck/build/manifest en plus des tests Rust workspace.
### Contrôles de réconciliation et d'horizon
Ces commandes sont volontairement courtes et indépendantes.
```bash
grep -F '## 0.0.3 (candidate)' CHANGELOG.md
grep -F -- '- `(h) 0.0.4` — fondation PostgreSQL partagée' ROADMAP.md
grep -F -- '- `(h) 0.0.6` — templates publics des trois sites' ROADMAP.md
grep -F -- '- `(h) 0.0.7` — templates d'administration des trois sites' ROADMAP.md
grep -F -- '- `(h) 0.0.8` — lecture publique Actix Web / Maud' ROADMAP.md
grep -F -- '- `(h) 0.0.9` — administration Actix Web / Maud et autorisation' ROADMAP.md
grep -F -- '- `(h) 0.0.10` — gestion, proposition, revue et publication' ROADMAP.md
test -f docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
test -f prompts/004-V0_0_4_START_PROMPT.md
grep -F '### 0.0.4 — PostgreSQL partagé' docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
grep -F '### 0.0.6 — templates publics' docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
grep -F "### 0.0.7 — templates d'administration" docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
grep -F '### 0.0.8 — Actix Web / Maud public' docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
grep -F 'pgr.sasedev.com affichage et gestion de PGR' docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
grep -F 'archive sasedev-knowledge-v0.0.3.zip téléchargée depuis le tag Gitea v0.0.3' prompts/004-V0_0_4_START_PROMPT.md
grep -F 'Ne pas rechercher, charger ni adopter automatiquement une nouvelle version de PGR.' prompts/004-V0_0_4_START_PROMPT.md
! grep -Eq '[0-9a-f]{40}|[0-9a-f]{64}' prompts/004-V0_0_4_START_PROMPT.md
test -z "$(git diff --name-only -- crates Web)"
test ! -e crates/knowledge-store-postgres-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e migrations
test ! -e history/0.0.3/beta.1.md
```
### Hygiène texte et lockfiles
```bash
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("docs"),Path("prompts"),Path("deltas"),Path("history")]; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file()]; bad=[str(p) for p in paths if (lambda b: b"\r" in b or not b.endswith(b"\n") or b.endswith(b"\n\n") or any(line != line.rstrip(" \t") for line in b.decode("utf-8").splitlines()))(p.read_bytes())]; print(f"Knowledge 0.0.3-beta.1 text hygiene: clean ({len(paths)} file(s))") if not bad else (_ for _ in ()).throw(AssertionError(bad))'
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then echo 'Cargo.lock must remain untracked for Knowledge' >&2; exit 1; fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
## Transition
### PASS
Produire `history/0.0.3/beta.1.md`, l'appliquer, commit/push `0.0.3-beta.1`, puis préparer `0.0.3-rc.1` sans nouvelle fonctionnalité. La RC doit geler le scope et rejouer la gate complète, y compris npm/Vite et les trois smokes HTTP.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history avant toute correction. Un défaut borné utilise `beta.1.fix.N`. Toute découverte remettant en cause l'intégrité du chemin Git, la mission `0.0.4` ou la trajectoire produit exige un REPLAN explicite avant poursuite.

154
deltas/0.0.3/rc.1.fix.1.md Normal file
View File

@@ -0,0 +1,154 @@
<!-- file: deltas/0.0.3/rc.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-rc.1.fix.1 — correction de la vérification des statuts historiques
## Base requise
État `0.0.3-rc.1` committé et poussé après application du history :
```text
history/0.0.3/rc.1.md = FAIL
```
La version devient :
```text
0.0.3-rc.1.fix.1
```
## Objet
Corriger uniquement l'assertion de validation qui a fait échouer `rc.1` alors que le history `alpha.1` porte bien sémantiquement le statut `PARTIAL`.
Le fichier historique `history/0.0.3/alpha.1.md` n'est pas modifié. Sa représentation `` `PARTIAL` `` reste une preuve historique valide.
Aucune fonctionnalité Git, Web, PostgreSQL, forge, auth ou workflow produit n'est ajoutée.
## Scope
La tranche remplace uniquement :
```text
VERSION
Cargo.toml
README.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
et ajoute :
```text
deltas/0.0.3/rc.1.fix.1.md
```
Aucun fichier n'est supprimé. `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/`, `prompts/` et tous les histories antérieurs sont gelés.
## Gate opérateur
Exécuter directement dans le shell interactif.
```bash
test "$(cat VERSION)" = '0.0.3-rc.1.fix.1'
grep -F 'version = "0.0.3-rc.1.fix.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.3/rc.1.md | grep -Fx 'FAIL'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-rc.1.fix.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test -p knowledge-git-lib --test git_write_push
cargo test --workspace --all-targets --all-features
```
## Contrôles spécifiques courts
Chaque commande est indépendante.
```bash
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.1.md | tr -d '`' | grep -Fx 'PARTIAL'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.1.fix.1.md | tr -d '`' | grep -Fx 'PARTIAL'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.1.fix.2.md | tr -d '`' | grep -Fx 'PASS'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.2.md | tr -d '`' | grep -Fx 'PASS'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.3.md | tr -d '`' | grep -Fx 'FAIL'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.3.fix.1.md | tr -d '`' | grep -Fx 'PASS'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/alpha.4.md | tr -d '`' | grep -Fx 'PASS'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/beta.1.md | tr -d '`' | grep -Fx 'PASS'
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/rc.1.md | tr -d '`' | grep -Fx 'FAIL'
test "$(find history/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 9
test "$(find deltas/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 10
test ! -e history/0.0.3/rc.1.fix.1.md
grep -F '## 0.0.3 (candidate)' CHANGELOG.md
grep -F -- '- `( ) 0.0.3` — écriture Git contrôlée et push gardé' ROADMAP.md
grep -F -- '- `(h) 0.0.4` — fondation PostgreSQL partagée' ROADMAP.md
test -f prompts/004-V0_0_4_START_PROMPT.md
test -z "$(git diff --name-only -- crates Web CHANGELOG.md ROADMAP.md docs/architecture docs/studies docs/rules prompts history)"
```
Vérifier l'hygiène texte :
```bash
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("docs"),Path("prompts"),Path("deltas"),Path("history")]; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file()]; bad=[str(p) for p in paths if (lambda b: b"\r" in b or not b.endswith(b"\n") or b.endswith(b"\n\n") or any(line != line.rstrip(" \t") for line in b.decode("utf-8").splitlines()))(p.read_bytes())]; print(f"Knowledge 0.0.3-rc.1.fix.1 text hygiene: clean ({len(paths)} file(s))") if not bad else (_ for _ in ()).throw(AssertionError(bad))'
```
Puis :
```bash
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
Le staging attendu est limité à :
```text
M Cargo.toml
M README.md
M VERSION
A deltas/0.0.3/rc.1.fix.1.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Transition
### PASS
Produire `history/0.0.3/rc.1.fix.1.md`, l'appliquer, commit/push `0.0.3-rc.1.fix.1`, puis préparer `0.0.3-rel.1` comme promotion mécanique stable pré-tag.
### FAIL
Produire le history exact puis un nouveau fix RC strictement borné au défaut observé. Ne pas promouvoir `rel.1` tant que la RC corrigée n'est pas `PASS`.

253
deltas/0.0.3/rc.1.md Normal file
View File

@@ -0,0 +1,253 @@
<!-- file: deltas/0.0.3/rc.1.md -->
<!-- version: 1 -->
# Delta 0.0.3-rc.1 — release candidate
## Base requise
État `0.0.3-beta.1` committé et poussé après application du history :
```text
history/0.0.3/beta.1.md = PASS
```
La version devient :
```text
0.0.3-rc.1
```
## Objet
Geler le scope de `0.0.3` et exécuter sa gate complète de release candidate.
Aucune nouvelle fonctionnalité, dépendance, crate, route, comportement Git, décision PostgreSQL/Web ou modification de l'horizon produit n'est introduite.
## Scope
La RC remplace uniquement :
```text
VERSION
Cargo.toml
README.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
et ajoute :
```text
deltas/0.0.3/rc.1.md
```
Aucun fichier n'est supprimé. `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/` et `prompts/` sont gelés.
## Histories attendus avant RC
La gate doit retrouver exactement les huit histories antérieurs suivants :
```text
alpha.1 PARTIAL
alpha.1.fix.1 PARTIAL
alpha.1.fix.2 PASS
alpha.2 PASS
alpha.3 FAIL
alpha.3.fix.1 PASS
alpha.4 PASS
beta.1 PASS
```
Pendant la gate, `rc.1.md` doit être le seul delta `0.0.3` sans history correspondant.
## Présentation candidate
La RC conserve :
```text
CHANGELOG.md -> ## 0.0.3 (candidate)
ROADMAP.md -> 0.0.3 encore engagée + horizon 0.0.4..0.0.10
prompt 004 -> handoff 0.0.4 présent, sans commit/SHA futur inventé
```
La présentation stable appartient à `rel.1`, pas à la RC.
## Gate opérateur
Exécuter directement dans le shell interactif. Ne pas activer `set -e`, `set -u` ou `set -o pipefail` globalement.
```bash
test "$(cat VERSION)" = '0.0.3-rc.1'
grep -F 'version = "0.0.3-rc.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.3/beta.1.md | grep -Fx 'PASS'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-rc.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
cargo test -p knowledge-git-lib --test git_write_candidate
cargo test -p knowledge-git-lib --test git_write_push
cargo test --workspace --all-targets --all-features
(cd Web/knowledge-template-lab && npm ci)
(cd Web/knowledge-template-lab && npm run typecheck)
(cd Web/knowledge-template-lab && npm run build)
python3 -c 'import json; from pathlib import Path; p=Path("../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json"); m=json.loads(p.read_text(encoding="utf-8")); e=m["src/main.ts"]; assert e.get("file"); assert e.get("css"); print("Knowledge 0.0.3-rc.1 Vite manifest audit: clean")'
```
## Contrôles d'intégrité courts
```bash
grep -A2 '^## Statut$' history/0.0.3/alpha.1.md | grep -Fx 'PARTIAL'
grep -A2 '^## Statut$' history/0.0.3/alpha.1.fix.1.md | grep -Fx 'PARTIAL'
grep -A2 '^## Statut$' history/0.0.3/alpha.1.fix.2.md | grep -Fx 'PASS'
grep -A2 '^## Statut$' history/0.0.3/alpha.2.md | grep -Fx 'PASS'
grep -A2 '^## Statut$' history/0.0.3/alpha.3.md | grep -Fx 'FAIL'
grep -A2 '^## Statut$' history/0.0.3/alpha.3.fix.1.md | grep -Fx 'PASS'
grep -A2 '^## Statut$' history/0.0.3/alpha.4.md | grep -Fx 'PASS'
grep -A2 '^## Statut$' history/0.0.3/beta.1.md | grep -Fx 'PASS'
test "$(find history/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 8
test "$(find deltas/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 9
test ! -e history/0.0.3/rc.1.md
grep -F '## 0.0.3 (candidate)' CHANGELOG.md
grep -F -- '- `( ) 0.0.3` — écriture Git contrôlée et push gardé' ROADMAP.md
grep -F -- '- `(h) 0.0.4` — fondation PostgreSQL partagée' ROADMAP.md
grep -F -- '- `(h) 0.0.10` — gestion, proposition, revue et publication' ROADMAP.md
test -f prompts/004-V0_0_4_START_PROMPT.md
! grep -Eq '[0-9a-f]{40}|[0-9a-f]{64}' prompts/004-V0_0_4_START_PROMPT.md
test ! -e crates/knowledge-store-postgres-lib
test ! -e crates/knowledge-forge-gitea-lib
test ! -e migrations
```
Vérifier le workspace Cargo sans bloc multi-lignes :
```bash
python3 -c 'import json; from pathlib import Path; m=json.loads(Path("/tmp/sasedev-knowledge-0.0.3-rc.1-metadata.json").read_text()); expected={"knowledge-app-lib","knowledge-domain-lib","knowledge-git-api","knowledge-git-lib","knowledge-sync-api","knowledge-web-lib","knowledge-pgr-server","knowledge-howtos-server","knowledge-snippets-server"}; assert {p["name"] for p in m["packages"]} == expected; assert len(m["workspace_members"]) == 9; print("Knowledge 0.0.3-rc.1 workspace audit: clean")'
```
Vérifier l'hygiène texte :
```bash
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("docs"),Path("prompts"),Path("deltas"),Path("history")]; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file()]; bad=[str(p) for p in paths if (lambda b: b"\r" in b or not b.endswith(b"\n") or b.endswith(b"\n\n") or any(line != line.rstrip(" \t") for line in b.decode("utf-8").splitlines()))(p.read_bytes())]; print(f"Knowledge 0.0.3-rc.1 text hygiene: clean ({len(paths)} file(s))") if not bad else (_ for _ in ()).throw(AssertionError(bad))'
```
## Smokes HTTP de production
Construire les trois launchers :
```bash
cargo build -p knowledge-pgr-server -p knowledge-howtos-server -p knowledge-snippets-server
```
Préparer le chemin du manifest et la liste des assets :
```bash
export KNOWLEDGE_RC_MANIFEST="$(realpath ../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json)"
python3 -c 'import json,os; m=json.load(open(os.environ["KNOWLEDGE_RC_MANIFEST"],encoding="utf-8")); e=m["src/main.ts"]; print("/"+e["file"].lstrip("/")); print(*["/"+x.lstrip("/") for x in e.get("css",[])],sep="\n")' > /tmp/sasedev-knowledge-0.0.3-rc.1-assets.txt
cat /tmp/sasedev-knowledge-0.0.3-rc.1-assets.txt
```
### PGR
```bash
env -u KNOWLEDGE_VITE_DEV_ORIGIN KNOWLEDGE_VITE_MANIFEST_PATH="$KNOWLEDGE_RC_MANIFEST" KNOWLEDGE_PGR_BIND_ADDRESS=127.0.0.1:18181 ../builds/sasedev-knowledge/target/debug/knowledge-pgr-server > /tmp/knowledge-pgr-rc.1.log 2>&1 & echo $! > /tmp/knowledge-pgr-rc.1.pid
sleep 1
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18181/healthz",timeout=2); assert r.status==200 and r.read().decode()=="ok"; print("pgr healthz PASS")'
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18181/",timeout=2); assert r.status==200; open("/tmp/knowledge-pgr-rc.1.html","wb").write(r.read())'
while IFS= read -r asset; do grep -F "$asset" /tmp/knowledge-pgr-rc.1.html >/dev/null && python3 -c 'import sys,urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18181"+sys.argv[1],timeout=2); assert r.status==200' "$asset" || exit 1; done < /tmp/sasedev-knowledge-0.0.3-rc.1-assets.txt
kill "$(cat /tmp/knowledge-pgr-rc.1.pid)"
wait "$(cat /tmp/knowledge-pgr-rc.1.pid)" 2>/dev/null || true
echo 'knowledge-pgr-server smoke PASS'
```
### HOWTOs
```bash
env -u KNOWLEDGE_VITE_DEV_ORIGIN KNOWLEDGE_VITE_MANIFEST_PATH="$KNOWLEDGE_RC_MANIFEST" KNOWLEDGE_HOWTOS_BIND_ADDRESS=127.0.0.1:18182 ../builds/sasedev-knowledge/target/debug/knowledge-howtos-server > /tmp/knowledge-howtos-rc.1.log 2>&1 & echo $! > /tmp/knowledge-howtos-rc.1.pid
sleep 1
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18182/healthz",timeout=2); assert r.status==200 and r.read().decode()=="ok"; print("howtos healthz PASS")'
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18182/",timeout=2); assert r.status==200; open("/tmp/knowledge-howtos-rc.1.html","wb").write(r.read())'
while IFS= read -r asset; do grep -F "$asset" /tmp/knowledge-howtos-rc.1.html >/dev/null && python3 -c 'import sys,urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18182"+sys.argv[1],timeout=2); assert r.status==200' "$asset" || exit 1; done < /tmp/sasedev-knowledge-0.0.3-rc.1-assets.txt
kill "$(cat /tmp/knowledge-howtos-rc.1.pid)"
wait "$(cat /tmp/knowledge-howtos-rc.1.pid)" 2>/dev/null || true
echo 'knowledge-howtos-server smoke PASS'
```
### Snippets
```bash
env -u KNOWLEDGE_VITE_DEV_ORIGIN KNOWLEDGE_VITE_MANIFEST_PATH="$KNOWLEDGE_RC_MANIFEST" KNOWLEDGE_SNIPPETS_BIND_ADDRESS=127.0.0.1:18183 ../builds/sasedev-knowledge/target/debug/knowledge-snippets-server > /tmp/knowledge-snippets-rc.1.log 2>&1 & echo $! > /tmp/knowledge-snippets-rc.1.pid
sleep 1
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18183/healthz",timeout=2); assert r.status==200 and r.read().decode()=="ok"; print("snippets healthz PASS")'
python3 -c 'import urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18183/",timeout=2); assert r.status==200; open("/tmp/knowledge-snippets-rc.1.html","wb").write(r.read())'
while IFS= read -r asset; do grep -F "$asset" /tmp/knowledge-snippets-rc.1.html >/dev/null && python3 -c 'import sys,urllib.request; r=urllib.request.urlopen("http://127.0.0.1:18183"+sys.argv[1],timeout=2); assert r.status==200' "$asset" || exit 1; done < /tmp/sasedev-knowledge-0.0.3-rc.1-assets.txt
kill "$(cat /tmp/knowledge-snippets-rc.1.pid)"
wait "$(cat /tmp/knowledge-snippets-rc.1.pid)" 2>/dev/null || true
echo 'knowledge-snippets-server smoke PASS'
```
## Scope gelé et staging
```bash
test -z "$(git diff --name-only -- crates Web CHANGELOG.md ROADMAP.md docs/architecture docs/studies docs/rules prompts)"
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
git add -A
git diff --cached --check
git status --short
```
Le staging final attendu avant history est limité à :
```text
M Cargo.toml
M README.md
M VERSION
A deltas/0.0.3/rc.1.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Transition
### PASS
Produire `history/0.0.3/rc.1.md`, l'appliquer, commit/push `0.0.3-rc.1`, puis préparer `0.0.3-rel.1` comme promotion mécanique stable pré-tag.
### FAIL
Produire le history avec le résultat exact puis une tranche `rc.1.fix.1` strictement bornée à la régression observée. Ne pas promouvoir la stable tant que la RC n'est pas `PASS`.

112
deltas/0.0.3/rel.1.fix.1.md Normal file
View File

@@ -0,0 +1,112 @@
<!-- file: deltas/0.0.3/rel.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.0.3 rel.1.fix.1 — complétion de la preuve de clôture stable
## Baseline et version
Baseline : `0.0.3-rel.1` avec `history/0.0.3/rel.1.md` appliqué, committé et poussé avec le statut `PARTIAL`.
Ce delta MUST NOT être appliqué avant ce commit.
Version produit avant et après application :
```text
0.0.3
```
`rel.1.fix.1` est une tranche opérationnelle de clôture ; elle ne change pas la version SemVer stable visée.
## Objet
Compléter uniquement les deux preuves absentes du transcript `rel.1` :
- audit explicite de l'ensemble exact du workspace à partir de `cargo metadata` ;
- audit explicite d'hygiène texte.
Toutes les preuves produit/runtime de `rel.1` — Rust, Git, Web, Vite et trois smokes HTTP — sont déjà `PASS` et sont conservées dans son history. Ce fix ne modifie aucun fichier concerné par ces preuves.
## Scope
REPLACE :
```text
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.3/rel.1.fix.1.md
```
DELETE : aucun.
Aucun changement sous `VERSION`, `Cargo.toml`, `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/` ou `prompts/`.
## Gate opérateur
Exécuter depuis la racine du dépôt :
```bash
test "$(cat VERSION)" = '0.0.3'
grep -F 'version = "0.0.3"' Cargo.toml
sed -n '/^## Statut$/{n;n;p;q;}' history/0.0.3/rel.1.md | tr -d '`' | grep -Fx 'PARTIAL'
git diff --check
python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history
cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.3-rel.1.fix.1-metadata.json
python3 -c 'import json; from pathlib import Path; m=json.loads(Path("/tmp/sasedev-knowledge-0.0.3-rel.1.fix.1-metadata.json").read_text()); expected={"knowledge-app-lib","knowledge-domain-lib","knowledge-git-api","knowledge-git-lib","knowledge-sync-api","knowledge-web-lib","knowledge-pgr-server","knowledge-howtos-server","knowledge-snippets-server"}; assert {p["name"] for p in m["packages"]} == expected; assert len(m["workspace_members"]) == 9; print("Knowledge 0.0.3-rel.1.fix.1 workspace audit: clean")'
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("docs"),Path("prompts"),Path("deltas"),Path("history")]; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file()]; bad=[str(p) for p in paths if (lambda b: b"\r" in b or not b.endswith(b"\n") or b.endswith(b"\n\n") or any(line != line.rstrip(" \t") for line in b.decode("utf-8").splitlines()))(p.read_bytes())]; print(f"Knowledge 0.0.3-rel.1.fix.1 text hygiene: clean ({len(paths)} file(s))") if not bad else (_ for _ in ()).throw(AssertionError(bad))'
test -z "$(git diff --name-only -- VERSION Cargo.toml crates Web CHANGELOG.md ROADMAP.md docs/architecture docs/studies docs/rules prompts)"
test ! -e history/0.0.3/rel.1.fix.1.md
test -f Cargo.lock
git check-ignore -q Cargo.lock
if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then
echo 'Cargo.lock must remain untracked for Knowledge' >&2
exit 1
fi
test -f Web/knowledge-template-lab/package-lock.json
! git show-ref --verify --quiet refs/tags/v0.0.3
test ! -e sasedev-knowledge-0.0.3-publish.001-report.md
git add -A
git diff --cached --check
git status --short
```
## Staging attendu
Avant ajout du history de ce fix :
```text
A deltas/0.0.3/rel.1.fix.1.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Transition
### PASS
Produire `history/0.0.3/rel.1.fix.1.md`, l'appliquer, puis créer le premier commit stable avec le message exact :
```text
0.0.3
```
Après push de `master`, créer/pousser le tag annoté `v0.0.3`, vérifier tag/commit/archive réellement publiés puis produire séparément le rapport post-tag `sasedev-knowledge-0.0.3-publish.001-report.md`.
### FAIL / PARTIAL / BLOCKED
Conserver le résultat réel dans son history et corriger uniquement la fermeture stable dans le fix suivant, sans créer le tag.

77
deltas/0.0.3/rel.1.md Normal file
View File

@@ -0,0 +1,77 @@
<!-- file: deltas/0.0.3/rel.1.md -->
<!-- version: 1 -->
# Delta 0.0.3 rel.1 — promotion stable pré-tag
## Baseline et version
Baseline : `0.0.3-rc.1.fix.1`, avec `history/0.0.3/rc.1.fix.1.md` appliqué et committé.
Ce delta MUST NOT être appliqué avant ce commit.
Version avant application :
```text
0.0.3-rc.1.fix.1
```
Version après application :
```text
0.0.3
```
`rel.1` identifie la tranche de clôture opérationnelle ; il ne fait pas partie de la version SemVer du produit.
## Objet
Promouvoir mécaniquement la candidate corrigée vers la stable `0.0.3` sans rouvrir le scope.
La tranche finalise la version stable, la présentation README/roadmap/changelog, l'index documentaire, le plan historique `0.0.3` et la matrice de validation. Elle engage `0.0.4` comme prochaine mission conformément à la revue d'horizon déjà acquise et conserve le prompt `004` préparé avant clôture.
Aucun fichier Rust, frontend, architecture, étude, règle normative ou prompt n'est modifié.
## Scope
REPLACE :
```text
VERSION
Cargo.toml
README.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
```
ADD :
```text
deltas/0.0.3/rel.1.md
```
DELETE : aucun.
Le history `rc.1.fix.1` est livré séparément. Le history `rel.1` sera produit uniquement après la gate opérateur.
## Présentation stable attendue
```text
VERSION 0.0.3
Cargo workspace version 0.0.3
ROADMAP (x) 0.0.3 ; ( ) 0.0.4
CHANGELOG ## 0.0.3
README 0.0.3 présentée comme stable
plan 003 plan historique de la stable 0.0.3
prompt 004 présent pour 0.0.4
```
Les versions `0.0.5` à `0.0.10` restent des horizons révisables.
## Gate pré-tag
Exécuter depuis la racine du dépôt les commandes fournies avec le delta. La gate doit couvrir : version/history précédent, audits Markdown, `cargo fmt --all` puis `--check`, audits Rust, metadata/check/Clippy, tests Git ciblés et workspace, npm/typecheck/build, manifest, cohérence stable/histories/deltas, hygiène texte, trois smokes HTTP, lockfiles et staging final.
Aucun tag `v0.0.3`, archive taggée ou rapport post-tag ne doit être déclaré comme existant pendant cette gate.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md -->
<!-- version: 5 -->
<!-- version: 12 -->
# Documentation SaseDev Knowledge
@@ -11,16 +11,34 @@
- [`architecture/001-PROJECT_PROFILE.md`](architecture/001-PROJECT_PROFILE.md) — baseline PGR, owners/skeletons applicables, décisions `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER` et nomenclature ;
- [`architecture/002-KNOWLEDGE_ARCHITECTURE.md`](architecture/002-KNOWLEDGE_ARCHITECTURE.md) — frontières application/domain, Git, forge, synchronisation, PostgreSQL, contenu et publication.
- [`architecture/003-PGR_ADOPTION_MAP.md`](architecture/003-PGR_ADOPTION_MAP.md) — provenance PGR `v0.1.3`, mapping d'adoption et owners locaux résultants.
## Règles locales
- [`rules/000-README.md`](rules/000-README.md) — index des owners normatifs autonomes Knowledge.
## Templates locaux
- [`templates/001-NEW_SESSION_PROMPT.md`](templates/001-NEW_SESSION_PROMPT.md) — reprise d'une session/version depuis la baseline Knowledge locale.
- [`templates/002-RELEASE_PLAN.md`](templates/002-RELEASE_PLAN.md) — structure canonique d'un plan multi-tranches.
- [`templates/003-OPERATOR_HANDOFF.md`](templates/003-OPERATOR_HANDOFF.md) — commandes entre history, commit/push et delta suivant.
- [`templates/004-RUST_LIBRARY_CRATE.md`](templates/004-RUST_LIBRARY_CRATE.md) — pattern de façade `lib.rs` et module propriétaire.
## Études
- [`studies/001-AUTH_ADMIN_CORPUS_STUDY.md`](studies/001-AUTH_ADMIN_CORPUS_STUDY.md) — analyse des fonctionnalités auth/admin présentes ou seulement préparées dans deux archives historiques inachevées.
- [`studies/002-ACTIX_MAUD_VITE_CORPUS_STUDY.md`](studies/002-ACTIX_MAUD_VITE_CORPUS_STUDY.md) — analyse du pattern Vite manifest / Actix / Maud de `actix-auth-app` et contrat de reprise ciblée pour Knowledge.
- [`studies/003-V0_0_1_FOUNDATION_RECONCILIATION.md`](studies/003-V0_0_1_FOUNDATION_RECONCILIATION.md) — réconciliation beta de la fondation, DEFER conservés et revue d'horizon engageant uniquement `0.0.2`.
- [`studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md`](studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md) — étude comparative Git de démarrage `0.0.2`, décision process et ownership du backend.
- [`studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md`](studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md) — réconciliation beta du chemin Git/synchronisation, `DEFER` conservés et revue d'horizon sans prochaine release réservée.
- [`studies/006-V0_0_3_GIT_WRITE_STUDY.md`](studies/006-V0_0_3_GIT_WRITE_STUDY.md) — décision d'engager l'écriture Git contrôlée, stratégie de worktree et invariants de concurrence.
- [`studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md`](studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md) — réconciliation beta du chemin Git d'écriture, DEFER reclassés et horizon produit des trois sites.
## Plans
- [`plans/001-V0_0_1_FOUNDATION_PLAN.md`](plans/001-V0_0_1_FOUNDATION_PLAN.md) — plan historique de la stable de fondation `0.0.1`.
- [`plans/002-V0_0_2_GIT_SYNC_PLAN.md`](plans/002-V0_0_2_GIT_SYNC_PLAN.md) — plan historique de la stable Git générique et synchronisation de lecture `0.0.2`.
- [`plans/003-V0_0_3_GIT_WRITE_PLAN.md`](plans/003-V0_0_3_GIT_WRITE_PLAN.md) — plan historique de la stable `0.0.3`, écriture Git contrôlée et push gardé.
## Validation
@@ -30,4 +48,6 @@ Les deltas et histories sont des artefacts de workflow, pas des substituts à ce
## Handoff
- [`../prompts/002-V0_0_2_START_PROMPT.md`](../prompts/002-V0_0_2_START_PROMPT.md) — prompt de reprise préparé pour `0.0.2`, à utiliser seulement après publication du tag `v0.0.1` et vérification post-tag `PASS`.
- [`../prompts/002-V0_0_2_START_PROMPT.md`](../prompts/002-V0_0_2_START_PROMPT.md) — handoff historique exécuté pour ouvrir `0.0.2` depuis `v0.0.1` publiée et vérifiée.
- [`../prompts/003-V0_0_3_START_PROMPT.md`](../prompts/003-V0_0_3_START_PROMPT.md) — prompt local de reprise de `0.0.3` depuis `v0.0.2` publiée et vérifiée.
- [`../prompts/004-V0_0_4_START_PROMPT.md`](../prompts/004-V0_0_4_START_PROMPT.md) — handoff préparé pendant `0.0.3` pour ouvrir `0.0.4` depuis la stable `v0.0.3` une fois son tag/archive et son rapport post-tag réellement publiés et vérifiés.

View File

@@ -1,158 +1,179 @@
<!-- file: docs/architecture/001-PROJECT_PROFILE.md -->
<!-- version: 9 -->
<!-- version: 18 -->
# Profil du projet Knowledge
## Baseline autoritaire
Le cadrage initial repose sur :
La stable produit depuis laquelle `0.0.3` est ouverte est :
```text
PGR stable tag v0.1.1
PGR archive SHA-256 caaf8f456f7f8520172ea7705fd0cf5c5bf0e942dc4a773410bcbe7fe4ce243a
PGR publish report pgr-0.1.1-publish.001-report.md = PASS
Knowledge prompt prompts/001-V0_0_1_START_PROMPT.md
Knowledge stable tag v0.0.2
Knowledge stable commit 183d354a67bec6d2d512c4898f5189759a6120b0
Knowledge archive SHA-256 0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce
Knowledge publish report sasedev-knowledge-0.0.2-publish.001-report.md = PASS
Knowledge start prompt prompts/003-V0_0_3_START_PROMPT.md
```
L'archive PGR observée possède `VERSION = 0.1.1`, `292` entrées ZIP, `256` fichiers, `36` répertoires, une racine unique `pgr/`, aucun symlink et aucun `.git`. Les `48` tests Python et les audits Markdown headers/tables/links de la stable ont été rejoués avant préparation de cette tranche et sont propres.
La fondation `0.0.1` a été construite sous PGR `v0.1.1`. Cette identité reste historique dans les artefacts `0.0.1`.
Cette constatation de prévalidation ne remplace aucune gate opérateur Knowledge.
Le démarrage explicite de `0.0.2` a réévalué PGR et a retenu la stable suivante comme source d'adoption :
## Nature de l'instanciation
```text
PGR stable tag v0.1.3
PGR stable commit fe80dbbb737390a7ea99df27d979bd59ccfb86e3
PGR archive SHA-256 f3a7dffd5fe0fd3542e6b5207fda0b94bca2bd05bd5a54542d0a08d635a5d6ee
PGR publish report pgr-0.1.3-publish.001-report.md = PASS
```
Knowledge est un nouveau projet. La procédure normative d'adoption d'un dépôt existant `230-ADOPTION.md` n'est donc pas activée comme owner de migration.
Cette référence est une provenance d'adoption figée. Après matérialisation des contrats retenus, la baseline normative de Knowledge est constituée des artefacts locaux du dépôt Knowledge. Une version ou session future ne consulte pas automatiquement le PGR courant ; une nouvelle comparaison PGR exige une réévaluation explicitement engagée.
Le prompt de démarrage exige néanmoins une matrice `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER`. Les mêmes libellés sont utilisés ci-dessous pour rendre explicite la relation entre PGR et les décisions fondatrices Knowledge :
`0.0.2-alpha.4` matérialise les owners locaux nécessaires et ferme cette adoption comme migration ponctuelle. Le mapping durable est [`003-PGR_ADOPTION_MAP.md`](003-PGR_ADOPTION_MAP.md).
L'archive PGR `v0.1.3` vérifiée possède `VERSION = 0.1.3`, `332` entrées ZIP, `290` fichiers, `42` répertoires, une racine unique `pgr/`, aucun symlink et aucun `.git`. Son rapport post-tag indique `48/48` tests Python et les audits Markdown propres.
Cette prévalidation ne remplace aucune gate opérateur Knowledge.
## Historique de l'adoption
Knowledge n'est plus un nouveau dépôt à instancier : `0.0.2-alpha.1` migre un projet existant de la baseline PGR `v0.1.1` vers `v0.1.3`.
L'owner PGR `230-ADOPTION` devient applicable à cette migration ; son contrat est instancié localement par les décisions suivantes :
- partir d'une baseline autoritaire observée ;
- sélectionner uniquement les owners réellement applicables ;
- classifier `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER` ;
- préserver les décisions locales compatibles ;
- ne pas réécrire les commits, deltas, histories ou documents historiques pour simuler une conformité rétroactive ;
- valider proportionnellement la migration.
Les classifications locales restent :
- `ADOPT` — contrat PGR applicable retenu ;
- `KEEP-LOCAL` — choix propre à Knowledge, non possédé par un owner PGR plus spécifique et compatible avec les contrats adoptés ;
- `OVERRIDE` — règle PGR applicable remplacée localement ;
- `KEEP-LOCAL` — choix propre à Knowledge, compatible et non possédé par un owner PGR plus spécifique ;
- `OVERRIDE` — règle PGR applicable remplacée ou raffinée localement avec justification ;
- `DEFER` — choix volontairement reporté avec condition de reprise.
## Owners et skeletons applicables
## Owners locaux après adoption
| Élément | Décision | Matérialisation | Motif |
|----------------------------------------------|---------------------------------|-----------------|----------------------------------------------|
| `200-GOVERNANCE` | `ADOPT` | `alpha.1` | lifecycle/version locale |
| `210-WORKFLOW` | `ADOPT` | `alpha.1` | plan/delta/history/handoff |
| `220-SKELETONS` | `ADOPT` pour l'instanciation | `alpha.1` | composition explicite sans copie mécanique |
| `300-DOCUMENTATION` | `ADOPT` | `alpha.1` | rôles documentaires et taxonomie utile |
| `400-VALIDATION` | `ADOPT` | `alpha.1` | preuves proportionnées et autorité opérateur |
| `500-LANGUAGES` + `510-RUST_CARGO` | `ADOPT` | `alpha.2+` | backend Rust/workspace Cargo contrôlé |
| `530-TYPESCRIPT` | `ADOPT` | `alpha.4` | frontend durable TypeScript |
| `540-JAVASCRIPT_PACKAGE_ECOSYSTEM` | `ADOPT` | `alpha.4` | package manager/lockfile/scripts explicites |
| `550-VITE` | `ADOPT` puisque Vite est retenu | `alpha.4` | dev server/build/cache/outputs |
| `560-CSS_SASS` | `ADOPT` | `alpha.4` | CSS/Sass et outputs |
| `700-FRAMEWORKS` | `ADOPT` | `alpha.5` | cadre générique des frameworks |
| `710-TAURI` | `DEFER` / non applicable | hors `0.0.1` | aucun host desktop/mobile Tauri |
| skeleton `001-PROJECT_GOVERNANCE_FOUNDATION` | `ADOPT` | `alpha.1` | fondation locale PGR |
| skeleton `002-RUST_CARGO_WORKSPACE` | `ADOPT` | `alpha.2` | workspace Rust contrôlé |
| skeleton `003-WEB_FRONTEND` | `ADOPT` | `alpha.4` | frontend indépendant du host |
| validation `005-WEB_FRONTEND_VALIDATION` | `ADOPT` | `alpha.4+` | restore/typecheck/build/smoke proportionnés |
Les responsabilités quotidiennes sont désormais possédées par :
Les profils Java/Maven/Gradle, Android et autres technologies absentes ne sont pas sélectionnés.
```text
docs/rules/200-GOVERNANCE.md
docs/rules/210-WORKFLOW.md
docs/rules/300-DOCUMENTATION.md
docs/rules/400-VALIDATION.md
docs/rules/510-RUST_CARGO.md
docs/rules/530-WEB_FRONTEND.md
```
Le détail de la migration depuis les owners PGR `v0.1.3`, y compris les décisions `KEEP-LOCAL`, les éléments non applicables et les skeletons uniquement historiques, est conservé dans [`003-PGR_ADOPTION_MAP.md`](003-PGR_ADOPTION_MAP.md). Aucun de ces owners locaux ne nécessite de consulter PGR pour être interprété.
## Matrice des décisions importantes
| Décision | Classification | Contrat retenu / condition de reprise |
|----------------------------------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------|
| Dépôt applicatif | `KEEP-LOCAL` | `sasedev-knowledge` ; workspace unique de la plateforme |
| Nom court / préfixe crates | `KEEP-LOCAL` | `knowledge` / `knowledge-` |
| Dépôt normatif | `KEEP-LOCAL` | dépôt existant `pgr`, jamais fusionné dans le dépôt applicatif |
| Dépôt HOWTO | `KEEP-LOCAL` | dépôt séparé `howtos` |
| Dépôt snippets | `KEEP-LOCAL` | dépôt séparé `snippets` |
| Nombre de sources Git | `KEEP-LOCAL` | au moins trois sources initiales, sans limite codée à trois |
| Git comme backend du contenu | `KEEP-LOCAL` | invariant d'architecture Knowledge |
| Forge | `KEEP-LOCAL` | capacité optionnelle derrière adapter ; aucune API de forge dans le domaine |
| Gitea | `DEFER` | premier adapter candidat ; reprendre lorsque des capacités forge-only sont implémentées |
| Synchronisation sans webhook | `KEEP-LOCAL` | explicite/polling constituent le socle ; hooks/webhooks sont des accélérateurs |
| PostgreSQL | `KEEP-LOCAL` | une base logique partagée pour l'état transversal et les projections ; jamais vérité du contenu Git publié |
| Rust stable / édition 2024 | `ADOPT` | profil Rust/Cargo PGR |
| Workspace Cargo | `ADOPT` | skeleton Rust/Cargo ; actif depuis `alpha.2` |
| `Cargo.lock` | `KEEP-LOCAL` | généré localement et ignoré ; les manifests Cargo restent le contrat versionné |
| 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 |
| Sass | `KEEP-LOCAL` + owner `560` adopté | styles source de la chaîne de validation Web et des assets réellement servis par les pages Maud |
| Bootstrap | `KEEP-LOCAL` | framework CSS retenu pour les templates de référence et le rendu Maud ; jamais règle PGR universelle |
| Racine(s) frontend | `KEEP-LOCAL` | package partagé `Web/knowledge-template-lab/` pour les références statiques et assets communs |
| Templates statiques Web | `KEEP-LOCAL` | harness de conception/validation ; le markup runtime est adapté et servi par Maud |
| Intégration manifest Vite / Maud | `KEEP-LOCAL` | matérialisée dans `knowledge-web-lib` en `alpha.5`, avec dev-origin/manifest/base-path configurables |
| Driver/ORM PostgreSQL | `DEFER` | reprendre à la première implémentation persistante réelle |
| Migrations PostgreSQL | `DEFER` | reprendre avec le choix de driver/tooling et le premier schéma réel |
| Moteur de recherche | `DEFER` | commencer par capacités PostgreSQL/index applicatif ; réévaluer sur besoin mesuré |
| Fournisseur d'auth externe | `DEFER` | reprendre lorsque le workflow utilisateur l'exige |
| Surface admin par application | `KEEP-LOCAL` | chaque serveur protège ses opérations privilégiées ; URL exacte encore différée |
| Quatrième application d'administration | `DEFER` | peut agréger du transverse plus tard mais ne remplace jamais les frontières admin locales |
| Modèle rôles/capabilities | `DEFER` | séparer identité partagée et autorisations métier avant de figer enums/scopes |
| Formats finaux HOWTO/snippet | `DEFER` | définir avec corpus réel ; ne pas figer un schéma sans contenu représentatif |
| Noms définitifs de toutes les crates | `DEFER` | seules les responsabilités et les trois launchers sont cadrés ; créer une crate uniquement avec ownership réel |
Aucun `OVERRIDE` n'est requis à ce stade.
La sélection Web `alpha.4` conserve une seule baseline partagée tant qu'aucune divergence réelle entre les trois façades ne justifie plusieurs packages. Les entrées spécifiques pourront être ajoutées dans ce package avant toute duplication de toolchain.
| Décision | Classification | Contrat retenu / condition de reprise |
|----------------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------|
| Dépôt applicatif | `KEEP-LOCAL` | `sasedev-knowledge` ; workspace unique de la plateforme |
| Nom court / préfixe crates | `KEEP-LOCAL` | `knowledge` / `knowledge-` |
| Dépôt normatif | `KEEP-LOCAL` | dépôt existant `pgr`, jamais fusionné dans le dépôt applicatif |
| Dépôt HOWTO | `KEEP-LOCAL` | dépôt séparé `howtos` |
| Dépôt snippets | `KEEP-LOCAL` | dépôt séparé `snippets` |
| Nombre de sources Git | `KEEP-LOCAL` | au moins trois sources initiales, sans limite codée à trois |
| Git comme backend du contenu | `KEEP-LOCAL` | invariant d'architecture Knowledge |
| Implémentation Git de `0.0.2` | `KEEP-LOCAL` | processus `git` contrôlé derrière `knowledge-git-api` ; décision réévaluable derrière le contrat |
| Configuration physique Git | `KEEP-LOCAL` | `knowledge-git-lib` matérialisée en `0.0.2-alpha.2` ; source locale/remote + racine d'état ; pas dans le domaine |
| Copies Git générées | `KEEP-LOCAL` | racine d'état explicite hors dépôt source, mirrors et snapshots adressés par clé sûre/révision |
| Écriture Git contrôlée `0.0.3` | `KEEP-LOCAL` | contrat distinct de la lecture, worktree isolé, commit explicite et push de branche gardé par état attendu |
| Rebase/merge automatique | `DEFER` | reprendre avec un workflow de proposition réellement matérialisé ; divergence = conflit en `0.0.3` |
| Partage du cache Git entre processus | `DEFER` | état root exclusif à une instance backend en `0.0.2` ; reprendre avec verrouillage inter-processus réel |
| Forge | `KEEP-LOCAL` | capacité optionnelle derrière adapter ; aucune API de forge dans le domaine |
| Gitea | `DEFER` | premier adapter candidat ; reprendre lorsque des capacités forge-only sont implémentées |
| Synchronisation sans webhook | `KEEP-LOCAL` | explicite/périodique convergent vers le même cas d'usage ; hooks/webhooks restent accélérateurs |
| Scheduler périodique concret | `DEFER` | créer seulement lorsque son owner runtime/lifecycle est démontré |
| Dernière révision observée en `0.0.2` | `KEEP-LOCAL` | fournie par l'appelant et retournée par le cas d'usage ; pas de pseudo-persistance locale |
| PostgreSQL | `KEEP-LOCAL` | une base logique partagée future pour état transversal/projections ; jamais vérité du contenu Git publié |
| Rust stable / édition 2024 | `ADOPT` | owner local `docs/rules/510-RUST_CARGO.md` |
| Workspace Cargo | `ADOPT` | configuration locale + owner `510-RUST_CARGO.md` |
| `Cargo.lock` | `KEEP-LOCAL` | généré localement et ignoré ; manifests Cargo versionnés |
| Actix Web | `KEEP-LOCAL` | trois launchers existants et composition partagée dans `knowledge-web-lib` |
| Maud | `KEEP-LOCAL` | rendu SSR commun dans `knowledge-web-lib` |
| TypeScript | `ADOPT` | owner local `docs/rules/530-WEB_FRONTEND.md` |
| Package manager frontend | `KEEP-LOCAL` | npm ; `package-lock.json` versionné |
| Vite | `KEEP-LOCAL` + contrat adopté | owner local Web ; build/preview de références et manifest consommé par Maud |
| Sass | `KEEP-LOCAL` + contrat adopté | owner local Web ; styles source et assets réellement servis |
| Bootstrap | `KEEP-LOCAL` | framework CSS local Knowledge, jamais règle PGR universelle |
| Racine frontend | `KEEP-LOCAL` | package partagé `Web/knowledge-template-lab/` |
| Templates statiques Web | `KEEP-LOCAL` | harness de conception/validation ; rendu runtime Maud |
| Intégration manifest Vite / Maud | `KEEP-LOCAL` | matérialisée dans `knowledge-web-lib` |
| Driver/ORM PostgreSQL | `DEFER` | reprendre à la première implémentation persistante réelle |
| Migrations PostgreSQL | `DEFER` | reprendre avec le premier schéma réel |
| Moteur de recherche | `DEFER` | commencer par capacités PostgreSQL/index applicatif lorsque ce scope sera engagé |
| Fournisseur d'auth externe | `DEFER` | reprendre lorsque le workflow utilisateur l'exige |
| Surface admin par application | `KEEP-LOCAL` | chaque serveur protège ses opérations privilégiées ; URL exacte différée |
| Quatrième application d'administration | `DEFER` | agréger seulement si une responsabilité transverse apparaît |
| Modèle rôles/capabilities | `DEFER` | reprendre avec les cas d'usage d'autorisation réels |
| Formats finaux HOWTO/snippet | `DEFER` | définir avec corpus réel |
| Autres noms de crates | `DEFER` | créer uniquement lorsque l'ownership est démontré |
## Nomenclature de crates
Les trois packages exécutables sont des boundaries produit retenus :
Les packages matérialisés sont :
```text
knowledge-pgr-server launcher Actix Web de la façade PGR
knowledge-howtos-server launcher Actix Web de la façade HOWTOs
knowledge-snippets-server launcher Actix Web de la façade snippets
knowledge-domain-lib types métier stables de famille/source
knowledge-app-lib cas d'usage applicatifs partagés, dont synchronisation de lecture
knowledge-git-api contrat passif GitRepository provider-neutral
knowledge-git-lib implémentation Git générique + mirror/refresh/resolve/snapshot + état local contrôlé
knowledge-sync-api contrat passif RepositoryEventSource
knowledge-web-lib composition Actix/Maud/Vite partagée
knowledge-pgr-server launcher PGR mince
knowledge-howtos-server launcher HOWTOs mince
knowledge-snippets-server launcher snippets mince
```
Ils sont matérialisés depuis `alpha.5`. Leurs `main.rs` restent minces et délèguent à `knowledge-web-lib`. Chacun conserve le contrat de pouvoir protéger sa propre surface d'administration métier lorsque cette surface sera implémentée.
`knowledge-git-lib` devient réelle en `0.0.2-alpha.2` puis implémente le chemin de lecture Git dans `alpha.3`. `knowledge-app-lib` devient réelle en `0.0.2-alpha.5` : l'orchestration refresh / résolution / comparaison / snapshot confirme une responsabilité application partagée distincte du backend Git et des launchers.
Les responsabilités matérialisées sont :
Les responsabilités suivantes restent candidates et non matérialisées :
```text
knowledge-domain-lib types métier stables de famille/source ; aucune dépendance Actix/Git-provider/forge/PostgreSQL
knowledge-git-api contrat passif GitRepository ; implémentations génériques remplaçables derrière cette frontière
knowledge-sync-api contrat passif RepositoryEventSource et signaux de réconciliation ; plusieurs mécanismes producteurs prévus
knowledge-web-lib composition Actix/Maud partagée, résolution Vite dev/manifest et service des assets
knowledge-pgr-server launcher PGR mince
knowledge-howtos-server launcher HOWTOs mince
knowledge-snippets-server launcher snippets mince
```
Les responsabilités suivantes restent candidates et ne deviennent des packages que lorsqu'elles possèdent un ownership réel :
```text
knowledge-app-lib orchestration des use cases transversaux, si la séparation apporte une valeur réelle
knowledge-git-lib implémentation Git générique de knowledge-git-api
knowledge-store-postgres-lib état applicatif PostgreSQL partagé
knowledge-forge-gitea-lib adapter Gitea optionnel
knowledge-logging-lib diagnostics partagés, seulement lorsque plusieurs runtimes en ont effectivement besoin
knowledge-logging-lib diagnostics partagés, seulement sur besoin multi-runtime réel
```
Règles de création :
- ne pas créer une crate uniquement pour refléter un diagramme ;
- ne pas utiliser `-api` sans frontière de contrat indépendante réellement nécessaire ;
- garder le domaine indépendant d'Actix, Gitea et PostgreSQL ;
- garder les adapters dépendants des contrats, jamais l'inverse ;
- garder le domaine indépendant d'Actix, Gitea, Git process et PostgreSQL ;
- garder les adapters/implémentations dépendants des contrats, jamais l'inverse ;
- ne pas dupliquer dans les trois serveurs une logique qui possède clairement un owner commun ;
- ne pas créer un service réseau supplémentaire lorsqu'une crate partagée suffit.
## Corpus auth/admin étudié en alpha.2
## Corpus auth/admin étudié en 0.0.1
Les archives historiques `actix-auth-app-master.zip` et `amahil-project-master.zip` sont des sources d'inspiration non autoritaires. Leur analyse est conservée dans [`../studies/001-AUTH_ADMIN_CORPUS_STUDY.md`](../studies/001-AUTH_ADMIN_CORPUS_STUDY.md).
Les archives historiques `actix-auth-app-master.zip` et `amahil-project-master.zip` restent des sources d'inspiration non autoritaires. Leur analyse est conservée dans [`../studies/001-AUTH_ADMIN_CORPUS_STUDY.md`](../studies/001-AUTH_ADMIN_CORPUS_STUDY.md).
Elles justifient de conserver séparées :
Elles justifient de conserver séparées l'identité/authentification partagée, l'autorisation privilégiée propre à chaque application et une éventuelle façade transverse ultérieure. Elles ne justifient toujours pas de créer une crate auth, de figer `/admin` ou `/login`, ni d'adopter OAuth2/OIDC/MFA pendant `0.0.2`.
- l'identité/authentification partagée de plateforme ;
- l'autorisation privilégiée propre aux cas d'usage de chaque application ;
- une éventuelle façade d'administration transversale ultérieure.
## Résultat de l'adoption PGR 0.1.3
Elles ne justifient pas encore de créer une crate auth, de figer `/admin` ou `/login`, ni d'adopter OAuth2/OIDC/MFA comme scope `0.0.1`.
`0.0.2-alpha.4` ferme l'adoption comme migration ponctuelle :
## Décisions à rouvrir
- les owners nécessaires sont adaptés et possédés localement ;
- les règles Web externes applicables sont consolidées en un owner Knowledge plutôt que copiées en quatre documents ;
- les skeletons restent une provenance d'instanciation, pas des dépendances normatives ;
- les scripts génériques utiles sont sélectionnés localement et les variantes locales restent autoritaires ;
- les futurs prompts de session repartent de Knowledge seul ;
- une nouvelle stable PGR n'a aucun effet tant qu'une réévaluation n'est pas explicitement planifiée ;
- les artefacts historiques `0.0.1` et les tranches antérieures de `0.0.2` restent inchangés.
La réconciliation `beta.1` confirme que les `DEFER` suivants restent légitimes : adapter Gitea, driver/migrations PostgreSQL, moteur de recherche, fournisseur d'identité externe, quatrième application d'administration, modèle détaillé rôles/capabilities, formats finaux HOWTO/snippet et noms de crates non encore possédés par une responsabilité réelle.
## Réconciliation beta et décisions différées
La prochaine release engagée `0.0.2` rouvre uniquement l'implémentation Git générique et la synchronisation réelle de lecture. Elle ne lève aucun des `DEFER` ci-dessus par elle-même.
`0.0.2-beta.1` confirme que le scope alpha est matérialisé par neuf packages Cargo : contrats domaine/Git/synchronisation, backend Git générique, orchestration application partagée, composition Web et trois launchers. Aucune responsabilité supplémentaire n'est nécessaire pour fermer la mission de lecture/synchronisation de `0.0.2`.
Un changement de scope produit, l'ajout d'un host natif ou une contrainte externe peut déclencher un REPLAN ; il ne justifie pas un choix anticipé.
Restent différés après `0.0.2` : adapter Gitea, PostgreSQL/migrations, moteur de recherche, auth/admin complète, quatrième application d'administration, modèle détaillé rôles/capabilities, formats finaux HOWTO/snippet, scheduler concret, support explicite submodules/LFS, politique de nettoyage/rétention des snapshots et partage concurrent de la même racine Git entre plusieurs processus.
La revue d'horizon `0.0.2-beta.1` n'avait réservé aucune release suivante. L'ouverture explicite de `0.0.3` a consommé uniquement le `DEFER` d'écriture Git contrôlée : worktree d'écriture isolé, commit candidat et push de branche gardé. Rebase/merge automatique et workflow complet de proposition restent différés.
La revue `0.0.3-beta.1` sélectionne désormais la fondation PostgreSQL partagée comme prochaine mission `0.0.4`, sans préchoisir son driver/ORM avant l'étude de démarrage. Elle réordonne ensuite l'horizon produit pour rendre visibles les phases de modèles/projections, templates publics, templates admin, intégration Actix/Maud puis workflow de gestion/publication. Gitea, scheduler, recherche avancée, quatrième application admin et partage multi-processus restent différés tant que leurs déclencheurs ne sont pas matérialisés.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md -->
<!-- version: 9 -->
<!-- version: 18 -->
# Architecture de fondation Knowledge
@@ -15,23 +15,25 @@ Le principe directeur reste :
## Workspace Rust matérialisé
`alpha.2` instancie le skeleton PGR `002-RUST_CARGO_WORKSPACE`. `alpha.3` matérialise les trois premières frontières passives :
```text
knowledge-domain-lib famille/source de contenu
knowledge-git-api GitRepository provider-neutral
knowledge-sync-api RepositoryEventSource et signal de réconciliation
```
`alpha.5` complète la fondation avec :
La stable `0.0.1` a matérialisé les premières frontières et le runtime Web :
```text
knowledge-domain-lib famille/source de contenu
knowledge-git-api GitRepository provider-neutral
knowledge-sync-api RepositoryEventSource et signal de réconciliation
knowledge-web-lib composition Actix/Maud et assets Vite
knowledge-pgr-server launcher PGR
knowledge-howtos-server launcher HOWTOs
knowledge-snippets-server launcher snippets
```
`0.0.2` ajoute deux owners réels sans changer la topologie multi-app :
```text
knowledge-git-lib backend Git générique : mirror/refresh/resolve/snapshot
knowledge-app-lib cas d'usage partagé de synchronisation Changed/Unchanged
```
Les deux crates `-api` sont justifiées par des implémentations réellement remplaçables ou multiples. Elles restent sans runtime, sans logging propre et sans dépendance à Actix, Gitea ou PostgreSQL.
Les tests unitaires Rust sont désormais placés hors de `src/` sous `unit_tests/`, conformément à la convention locale Knowledge sélectionnée sur l'option permise par PGR.
@@ -69,9 +71,9 @@ knowledge-pgr-server knowledge-howtos-server knowledge-snippets-server
La dépendance pointe vers les contrats métier. Les détails Actix, processus Git, API Gitea ou SQL ne remontent pas dans le domaine.
`GitRepository` ne porte que les opérations génériques actuellement nécessaires : rafraîchir la vue locale d'une source et résoudre une référence opaque vers une révision Git autoritaire. Le contrat ne suppose ni API de forge, ni type de remote particulier, ni format SHA figé.
`GitRepository` ne porte dans la stable `0.0.1` que les opérations génériques alors nécessaires : rafraîchir la vue locale d'une source et résoudre une référence opaque vers une révision Git autoritaire. `0.0.2` n'étend ce contrat que lorsqu'un besoin de snapshot réellement implémenté le démontre. Le contrat ne suppose ni API de forge, ni type de remote particulier, ni format SHA figé.
`RepositoryEventSource` ne produit qu'un `RepositorySyncSignal` associé à un `source_id`. La cause est volontairement grossière (`Explicit`, `Periodic`, `ExternalSignal`) afin que hook Git, webhook de forge ou watcher ne deviennent pas des concepts autoritaires du domaine.
`RepositoryEventSource` ne produit qu'un `RepositorySyncSignal` associé à un `source_id`. La cause reste volontairement grossière (`Explicit`, `Periodic`, `ExternalSignal`) afin que hook Git, webhook de forge ou watcher ne deviennent pas des concepts autoritaires du domaine.
## 1. Applications publiques
@@ -105,32 +107,99 @@ Le domaine ne suppose pas qu'une famille possède exactement un repository. Il m
## 3. Git générique
Le contrat conceptuel `GitRepository` couvre uniquement les opérations Git nécessaires :
Le contrat `GitRepository` reste provider-neutral. `0.0.2-alpha.1` sélectionne comme première implémentation un **processus `git` contrôlé** et `0.0.2-alpha.2` matérialise son premier owner runtime :
```text
refs / resolve revision
fetch
checkout ou worktree local
status/diff
branch
commit
merge/rebase contrôlé lorsque le workflow l'exige
push lorsque autorisé
knowledge-git-lib
```
L'implémentation doit fonctionner sans connaître Gitea, GitHub ou GitLab.
Cette sélection est une décision locale Knowledge, pas un couplage du contrat au CLI Git. Une autre implémentation pourra remplacer le backend ultérieurement si des preuves de maintenance, performance ou portabilité le justifient.
Le serveur préfère des mirrors/clones et worktrees locaux. Une requête publique ne doit pas déclencher un appel réseau vers la forge.
### Configuration physique
La baseline prévoit au moins trois sources Git indépendantes :
`knowledge-domain-lib` continue de posséder `SourceId` et `ContentSource`. Les URLs, chemins locaux, exécutable Git et répertoires de travail ne remontent pas dans le domaine.
`knowledge-git-lib` possède la configuration physique :
```text
pgr source primaire de knowledge-pgr-server
howtos source primaire de knowledge-howtos-server
snippets source primaire de knowledge-snippets-server
source_id -> local path ou remote Git générique
source_id -> référence cible configurée
backend -> racine d'état Git contrôlée
```
Ce mapping est configurable et extensible ; il ne devient pas un `match` codé en dur sur trois repositories.
La composition runtime pourra charger ces valeurs depuis la configuration de déploiement. La persistance logique future dans PostgreSQL reste différée.
### Matérialisation locale
Les données générées résident hors du dépôt applicatif et hors des dépôts éditoriaux :
```text
<git-state-root>/
empty-global.gitconfig
hooks-disabled/
repositories/
<source-key>.git/
snapshots/
<source-key>/
<resolved-revision>/
write-worktrees/
<source-key>/
candidate/
```
Le backend reçoit explicitement `<git-state-root>` ; il n'utilise aucun défaut implicite sous le workspace. Une variable de déploiement telle que `KNOWLEDGE_GIT_STATE_ROOT` pourra alimenter la composition, mais la librairie reçoit un chemin et ne lit pas elle-même l'environnement.
`source-key` est une clé sûre dérivée par l'implémentation. En `alpha.2`, elle encode de manière déterministe les octets UTF-8 du `SourceId` sous une forme hexadécimale préfixée ; un `SourceId` ou une URL remote ne sont jamais interpolés directement comme chemin de filesystem.
Pendant `0.0.2`, une racine d'état appartient exclusivement à une instance backend/processus. Le partage simultané d'une même racine entre plusieurs processus reste `DEFER` jusqu'à matérialisation d'un verrouillage inter-processus.
### Mirror et refresh
Une source locale ou remote converge vers un mirror Git contrôlé. `alpha.2` implémente l'initialisation et l'inspection bare minimale ; `alpha.3` ajoute l'enregistrement explicite des sources physiques, le refresh réel, la résolution et le snapshot derrière `GitRepository`.
L'initialisation utilise conceptuellement :
```text
git clone --mirror [--no-local pour une source locale] -- <source> <mirror>
```
Pour une source locale, `--no-local` évite de créer une dépendance par hardlinks/optimisation locale sur le repository éditorial.
Le refresh réutilise la configuration du remote du mirror et exécute `remote update --prune`. L'opération est idempotente, initialise le mirror s'il n'existe pas encore et ne dépend d'aucune API de forge.
### Résolution et snapshot
Une référence cible n'est jamais considérée comme identité autoritaire avant résolution. Le backend exige qu'elle se résolve vers un commit réel, selon la sémantique :
```text
git --git-dir=<mirror> rev-parse --verify --end-of-options <reference>^{commit}
```
La valeur obtenue devient `GitRevision`. Le backend valide que l'identité produite est une représentation hexadécimale non vide avant de l'utiliser comme composant de chemin, sans figer une longueur SHA particulière.
Le snapshot de lecture est ensuite créé comme worktree détaché adressé par cette révision. `GitSnapshot` expose uniquement la révision autoritaire et le chemin local de lecture. Un snapshot déjà présent n'est réutilisé qu'après vérification de son `HEAD` et d'un état propre ; une divergence produit une erreur récupérable plutôt qu'un nettoyage destructif silencieux.
Les hooks Git sont désactivés pour les opérations de checkout/worktree appartenant au backend. Les commandes sont exécutées directement sans shell, avec prompts interactifs désactivés et sans journaliser de credential.
### Écriture Git engagée en `0.0.3`
`0.0.3` ajoute une frontière d'écriture distincte du `GitRepository` de lecture. Le backend conserve le processus `git` contrôlé et matérialise un worktree d'écriture isolé sous la racine d'état, sans modifier les snapshots de lecture.
Le chemin cible est :
```text
base GitRevision explicite
-> worktree d'écriture contrôlé
-> mutations de fichiers confinées
-> commit avec identité explicite
-> révision candidate
-> push vers refs/heads/... gardé par l'état distant attendu
-> succès ou conflit explicite
```
Une divergence ne déclenche ni rebase, ni merge, ni force-push non gardé. Ces décisions appartiennent à un futur workflow de proposition/publication. Les capacités forge-specific restent séparées.
La baseline conserve au moins trois sources indépendantes (`pgr`, `howtos`, `snippets`) mais le mapping reste configurable et extensible ; aucun `match` codé en dur n'impose exactement trois repositories.
## 4. Forge optionnelle
@@ -150,9 +219,9 @@ Gitea est le premier candidat d'intégration mais n'est pas figé comme dépenda
## 5. Événements et synchronisation
Le contrat conceptuel `RepositoryEventSource` produit un **signal de synchronisation**, pas une vérité de contenu.
Le contrat `RepositoryEventSource` produit un **signal de synchronisation**, pas une vérité de contenu.
Sources prévues :
Sources conceptuelles :
```text
sync explicite
@@ -162,22 +231,28 @@ webhook de forge
watcher filesystem éventuel
```
Toutes convergent vers le même cas d'usage interne :
Toutes les sources doivent converger vers le même cas d'usage :
```text
signal(source_id)
-> fetch/refresh si nécessaire
-> refresh du mirror si nécessaire
-> résolution de la révision cible Git
-> comparaison avec la dernière révision indexée
-> checkout/worktree de la révision
-> parsing + validation selon la famille
-> construction d'une projection/index
-> publication atomique de l'état dérivé
-> comparaison avec previous_revision
-> snapshot/worktree de cette révision si changement
-> futurs parsing + validation + projection
```
Un webhook peut être perdu ou dupliqué. Le commit Git résolu reste l'identité autoritaire de la révision.
Un webhook peut être perdu ou dupliqué. Le commit Git résolu reste l'identité autoritaire.
La synchronisation peut être déclenchée par chacun des trois serveurs au début. Un worker séparé n'est créé que si un besoin d'ownership/lifecycle distinct apparaît.
### Ownership de `0.0.2`
La synchronisation partagée est une responsabilité de couche application, distincte du backend Git et des producteurs de signaux. `0.0.2-alpha.5` confirme cette responsabilité et matérialise `knowledge-app-lib` comme owner du cas d'usage provider-neutral ; les launchers n'ont pas à dupliquer cette orchestration.
Sans PostgreSQL dans cette release, la dernière révision connue n'est pas stockée dans un fichier ad hoc caché. Le cas d'usage reçoit `previous_revision` de l'appelant et retourne explicitement la révision observée ainsi qu'un résultat `Changed` ou `Unchanged`. La future persistance pourra remplacer cet état appelant sans modifier la frontière Git.
La cause `Periodic` reste supportée par `knowledge-sync-api`, mais `0.0.2-alpha.1` ne désigne pas encore de scheduler concret. Un scheduler n'est matérialisé que si son lifecycle/runtime est démontré ; lorsqu'il existe, il appelle exactement le même cas d'usage que la voie explicite.
La synchronisation peut rester composée par chacun des trois serveurs au début. Un worker réseau séparé n'est créé que si un owner/lifecycle distinct devient nécessaire.
## 6. Propositions et revues
@@ -275,9 +350,9 @@ 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.
`0.0.1-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.
`0.0.1-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.
@@ -291,7 +366,38 @@ La fondation matérialisée est cohérente avec les objectifs `0.0.1` :
- frontend de référence indépendant et rendu runtime Maud effectivement relié au manifest Vite ;
- aucune persistance PostgreSQL, auth complète, UI admin complète, forge spécifique ou format final HOWTO/snippet introduit par anticipation.
Les prochaines capacités sont donc reprises par releases séparées. `0.0.2` engage uniquement le chemin Git générique de lecture et la synchronisation réelle ; les autres axes restent différés.
`0.0.2-alpha.1` a conservé cette réconciliation comme baseline et la stable `0.0.2` a fermé le chemin Git générique de lecture et la synchronisation réelle.
## Réconciliation `0.0.2-beta.1`
La mission Git/synchronisation est réconciliée avec l'implémentation réellement obtenue :
- neuf packages Cargo, sans crate placeholder ;
- `knowledge-git-api` provider-neutral et `knowledge-git-lib` comme implémentation process Git contrôlée ;
- sources locales/remotes convergeant vers un mirror contrôlé puis vers une révision commit et un snapshot validé ;
- `knowledge-app-lib` comme unique owner du cas d'usage refresh / resolve / compare / snapshot ;
- dernière révision fournie par l'appelant, sans stockage caché ;
- causes de synchronisation non autoritaires, `Periodic` sans scheduler imposé ;
- règles Knowledge autonomes après l'adoption ponctuelle PGR `v0.1.3` ;
- aucun adapter forge, PostgreSQL, auth/admin complète, écriture Git ou format final HOWTO/snippet introduit par anticipation.
Les besoins de rétention de snapshots, submodules/LFS, partage multi-processus de la racine Git et scheduler concret restent différés jusqu'à preuve d'un besoin réel. Aucune prochaine release n'est numérotée par cette réconciliation.
## Ouverture `0.0.3` — écriture Git contrôlée
Après publication vérifiée de `v0.0.2`, la prochaine mission explicitement engagée est l'écriture Git générique contrôlée. Elle réutilise le backend process existant, introduit un contrat d'écriture distinct de la lecture et garde la forge optionnelle.
`0.0.3` borne la mise à jour distante à des branches `refs/heads/...` et exige un état distant attendu. Un push n'est accepté que lorsque cette attente reste vraie ; sinon le résultat est un conflit récupérable. Le backend ne résout pas ce conflit par rebase/merge automatique.
`alpha.2` matérialise cette frontière dans `knowledge-git-api` sans implémentation de backend : `GitMutationPath` confine lexicalement les mutations à des chemins relatifs normalisés hors `.git`, `GitFileMutation` représente écriture ou suppression, `GitCommitIdentity` impose une identité explicite, `GitBranch` borne la cible à une référence de branche complète valide, et `GitRemoteState` représente soit l'absence attendue soit une révision distante attendue. `GitWriteRequest`, `GitPushRequest` et `GitPushOutcome` portent respectivement la création de candidate, la comparaison-et-échange distante et son résultat. Le trait `GitRepositoryWriter` reste séparé de `GitRepository` afin qu'un consommateur ou fake lecture seule n'acquière aucune obligation d'écriture.
`alpha.3` implémente la moitié locale de ce contrat dans `knowledge-git-lib`. Le backend crée un worktree détaché temporaire sous `write-worktrees/<source-key>/candidate`, applique uniquement les chemins demandés et refuse tout composant filesystem qui est un symlink. Le staging est contrôlé contre l'ensemble exact des mutations avant commit. L'identité auteur/committer est injectée explicitement ; la configuration Git système et globale est neutralisée, les hooks sont redirigés vers `hooks-disabled`, et le commit désactive également hook/signature à l'appel.
Après commit, le backend vérifie `HEAD` et `HEAD^1`, exige que le parent soit exactement la `GitRevision` de base puis conserve l'objet candidat sous `refs/knowledge/candidates/<revision>`. Le worktree possédé est retiré après succès ; après une erreur survenue dans un worktree créé par l'opération, le backend tente une suppression forcée de cet état temporaire. Un worktree préexistant n'est jamais supprimé silencieusement et produit une erreur récupérable.
Le push distant gardé reste réservé à `alpha.4`. `alpha.3` ne met à jour aucune branche remote et n'implémente ni rebase ni merge.
PostgreSQL, workflow complet de proposition, auth/admin, parsing final et adapter Gitea restent des missions distinctes.
## 10. Administration
@@ -350,9 +456,9 @@ Une pratique répétée dans HOWTO/snippet peut devenir un candidat d'étude PGR
- utilisateurs/sessions ;
- propositions et revue ;
- commentaires/audit applicatif ;
- configuration logique des sources ;
- configuration logique persistante des sources lorsque PostgreSQL sera matérialisé ;
- mapping source/famille/application ;
- dernière révision synchronisée/indexée par source ;
- dernière révision synchronisée/indexée par source lorsque la persistance sera matérialisée ;
- index et recherche dérivés ;
- relations calculées entre familles ;
- jobs de synchronisation/publication ;
@@ -372,3 +478,53 @@ snippets source Git courte/contextualisée séparée
```
Les dépôts de contenu ne sont pas fusionnés dans PGR et ne sont pas requis comme submodules Git du dépôt applicatif. Leur localisation et leurs credentials sont de la configuration de déploiement.
Pendant `0.0.2`, cette configuration de déploiement est adaptée en configuration physique possédée par `knowledge-git-lib`. La racine de mirrors/worktrees reste un état généré reconstructible et ne remplace pas le futur stockage logique PostgreSQL.
## Écriture Git gardée acquise en 0.0.3-alpha.4
Le backend Git complète désormais le cycle provider-neutral d'écriture sans adapter de forge : un candidat retenu localement peut être publié vers une branche `refs/heads/...` uniquement sous garde explicite de l'état distant attendu.
Le chemin est :
```text
candidat retenu sous refs/knowledge/candidates/<revision>
-> observation `git ls-remote --refs origin <branche>`
-> comparaison avec GitRemoteState attendu
-> git push --force-with-lease=<branche>:<attendu>
-> Updated(candidate) ou Conflict { actual }
```
Pour `GitRemoteState::Absent`, la lease utilise une attente vide et autorise donc uniquement la création d'une branche encore absente. Pour `GitRemoteState::Revision(r)`, la mise à jour distante n'est acceptée que si la branche pointe encore exactement sur `r`. L'observation préalable fournit un conflit rapide ; `--force-with-lease` reste la garde autoritaire contre une course entre observation et push. Après un rejet de push, le backend observe à nouveau la branche afin de traduire une divergence concurrente en conflit récupérable.
Cette capacité ne fait aucun rebase/merge automatique et ne publie aucune pull/merge request. Elle utilise uniquement le remote `origin` possédé par le mirror contrôlé et conserve la forge hors du contrat.
## Réconciliation `0.0.3-beta.1` et trajectoire produit
Le chemin Git générique est maintenant complet pour la responsabilité de `0.0.3` : lecture/snapshot par `GitRepository`, création locale de candidat puis push gardé par `GitRepositoryWriter`, sans API de forge. Les deux contrats restent séparés et les conflits distants sont des résultats récupérables plutôt que des rebase/merge implicites.
La prochaine dépendance structurante commune aux trois produits n'est plus l'écriture Git mais l'état applicatif persistant partagé. `0.0.4` est donc retenue comme prochaine mission pour matérialiser PostgreSQL, son premier schéma et ses migrations, la configuration logique des sources et l'état minimal de synchronisation/indexation. Le choix concret du driver/ORM, du pooling et de la stratégie de test PostgreSQL reste à décider dans l'étude de démarrage `0.0.4` ; il n'est pas anticipé par `0.0.3`.
La trajectoire des façades Web est désormais explicitement séparée en étapes afin de ne pas confondre design, rendu et logique serveur :
```text
PostgreSQL partagé
-> modèles/projections PGR + HOWTO + snippet
-> templates publics des trois sites dans le template lab
-> templates d'administration des trois sites dans le template lab
-> routes publiques Actix + adaptation Maud des templates publics
-> auth/autorisation + routes admin Actix + adaptation Maud des templates admin
-> gestion/proposition/revue/publication appuyée sur Git + PostgreSQL
```
Les pages de garde, listes, détails, navigation et états de recherche/filtrage visibles sont donc conçus d'abord comme templates Web de référence. Les dashboards, formulaires et états de proposition/revue suivent le même principe côté administration. Une phase ultérieure adapte le markup validé en composants Maud servis par les trois launchers Actix, plutôt que de développer simultanément deux interfaces indépendantes.
Cette trajectoire concerne les trois sites :
```text
pgr.sasedev.com consultation et gestion du référentiel PGR
howtos.sasedev.com consultation et gestion des HOWTOs
snippets.sasedev.com consultation et gestion des snippets
```
Git reste la vérité éditoriale publiée ; PostgreSQL porte l'état transversal et les projections reconstructibles. Un contenu accepté n'est publié qu'après production d'une révision Git autoritaire, puis la projection locale est réconciliée depuis cette révision.

View File

@@ -0,0 +1,136 @@
<!-- file: docs/architecture/003-PGR_ADOPTION_MAP.md -->
<!-- version: 1 -->
# Mapping d'adoption PGR v0.1.3 vers Knowledge
## Objet
Ce document ferme la réconciliation PGR explicitement engagée au démarrage de `0.0.2`. Il conserve la provenance des contrats examinés et indique leur owner Knowledge durable.
Après cette tranche, le développement courant se fonde sur les artefacts locaux Knowledge. Les règles sélectionnées ci-dessous sont adaptées et suffisamment matérialisées localement pour leur usage quotidien ; ce mapping ne crée aucune dépendance de lecture vers PGR.
## Source d'adoption observée
```text
PGR tag v0.1.3
PGR commit fe80dbbb737390a7ea99df27d979bd59ccfb86e3
PGR archive SHA-256 f3a7dffd5fe0fd3542e6b5207fda0b94bca2bd05bd5a54542d0a08d635a5d6ee
publish report pgr-0.1.3-publish.001-report.md = PASS
```
Cette source sert uniquement à expliquer les décisions ci-dessous.
## Décisions ADOPT
### Gouvernance — provenance PGR owner 200
Décision : `ADOPT`.
Owner local : [`../rules/200-GOVERNANCE.md`](../rules/200-GOVERNANCE.md).
Adaptation : proportionnalité, versioning/lifecycle, fixes, REPLAN, baseline autoritaire, décisions différées et autonomie normative sont exprimés sans dépendre d'un dépôt PGR courant.
### Workflow — provenance PGR owner 210
Décision : `ADOPT`.
Owner local : [`../rules/210-WORKFLOW.md`](../rules/210-WORKFLOW.md).
Adaptation : contrats delta/history, cycle gate/commit/push, prompts/handoffs, fixes, REPLAN, fermeture stable, tag/archive et preuve post-tag sont spécialisés pour Knowledge et `origin/master` lorsqu'il est établi.
### Adoption d'un projet existant — provenance PGR owner 230
Décision : `ADOPT`, puis scope fermé après matérialisation.
Owner local durable : gouvernance locale et présent mapping.
Adaptation : toute future adoption externe doit être explicitement engagée ; aucune surveillance continue de PGR n'est créée.
### Documentation — provenance PGR owner 300
Décision : `ADOPT`.
Owner local : [`../rules/300-DOCUMENTATION.md`](../rules/300-DOCUMENTATION.md).
Adaptation : rôles README/RULES/ROADMAP/CHANGELOG/index, familles documentaires, documents vivants/historiques, headers, plans, revue d'horizon et prompts sont possédés localement.
### Validation — provenance PGR owner 400
Décision : `ADOPT`.
Owner local : [`../rules/400-VALIDATION.md`](../rules/400-VALIDATION.md).
Adaptation : validation proportionnée, statuts, catégories de preuve, autorité opérateur, ordre des gates et scripts read-only sont possédés localement.
### Rust / Cargo — provenance PGR owners 500 + 510
Décision : `ADOPT` pour le profil Rust applicable ; le méta-owner langage 500 n'est pas matérialisé séparément.
Owner local : [`../rules/510-RUST_CARGO.md`](../rules/510-RUST_CARGO.md).
Adaptation : Rust 2024, workspace, nomenclature, façade crate-root, tests et audits stricts sont exprimés directement pour Knowledge.
### Web — provenance PGR owners 530 + 540 + 550 + 560
Décision : `ADOPT` pour les contrats réellement utilisés.
Owner local consolidé : [`../rules/530-WEB_FRONTEND.md`](../rules/530-WEB_FRONTEND.md).
Adaptation : TypeScript, npm, Vite et Sass sont regroupés avec les décisions locales Bootstrap et Maud afin d'éviter quatre owners locaux artificiellement séparés.
## Décisions KEEP-LOCAL
### Skeletons — provenance PGR owner 220
Décision : `KEEP-LOCAL` après instanciation.
Les skeletons de fondation, workspace Rust et frontend ont servi à amorcer l'état existant. Ils ne sont pas réappliqués ni conservés comme dépendances normatives. Les fichiers réellement instanciés dans Knowledge sont les propriétaires de leur configuration.
### Frameworks — provenance PGR owner 700
Décision : `KEEP-LOCAL`.
Le méta-owner framework n'apporte pas de règle quotidienne supplémentaire : Actix Web, Maud et Bootstrap sont des choix locaux documentés dans `RULES.md`, l'architecture et l'owner Web local.
### Audits Rust déjà adaptés
Décision : `KEEP-LOCAL` lorsque les scripts locaux divergent ou spécialisent la baseline générique.
Les audits Rust présents dans `scripts/` sont les copies autoritaires Knowledge. Ils ne sont pas remplacés automatiquement par des scripts PGR portant le même nom.
## Décisions DEFER / non applicables
- `710-TAURI` : `DEFER`, aucun host Tauri dans le scope courant.
- Java/Maven/Gradle : non applicables au workspace actuel.
- Android : non applicable au produit Web actuel.
- autres plateformes/langages absents : non sélectionnés.
Aucun `OVERRIDE` de règle PGR applicable n'est nécessaire pour clôturer cette adoption ; les décisions produit distinctes sont classées `KEEP-LOCAL` plutôt que présentées comme des overrides artificiels.
## Artefacts localement matérialisés
L'adoption est considérée autonome lorsque les artefacts suivants sont présents et validés :
```text
RULES.md
docs/rules/200-GOVERNANCE.md
docs/rules/210-WORKFLOW.md
docs/rules/300-DOCUMENTATION.md
docs/rules/400-VALIDATION.md
docs/rules/510-RUST_CARGO.md
docs/rules/530-WEB_FRONTEND.md
docs/templates/001-NEW_SESSION_PROMPT.md
docs/templates/002-RELEASE_PLAN.md
docs/templates/003-OPERATOR_HANDOFF.md
docs/templates/004-RUST_LIBRARY_CRATE.md
scripts/audit_markdown_headers.py
scripts/audit_markdown_links.py
scripts/audit_markdown_tables.py
scripts/audit_rust_crate_root_facades.py
```
Les configurations déjà instanciées (`Cargo.toml`, `rustfmt.toml`, `clippy.toml`, `.cargo/config.toml`, package npm, tsconfig et Vite) restent leurs propres artefacts autoritaires ; elles ne sont pas dupliquées dans un répertoire de templates uniquement pour reproduire PGR.
## Critère de fermeture
Après `PASS` d'`alpha.4`, une nouvelle session Knowledge doit pouvoir comprendre et développer le projet à partir de la stable Knowledge et de ses artefacts locaux, sans charger PGR. Une future adoption PGR demeure possible, mais seulement comme opération explicitement planifiée.

View File

@@ -0,0 +1,324 @@
<!-- file: docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md -->
<!-- version: 15 -->
# Plan 0.0.2 — Git générique et synchronisation de lecture
## Statut et sources
Plan historique de la stable `0.0.2`, ouverte depuis la stable Knowledge `v0.0.1` publiée et vérifiée.
Sources autoritaires de reprise :
```text
Knowledge tag stable v0.0.1
Knowledge commit stable 98ec46fe73bd50dacbe6be220650d87dd2000b74
Knowledge archive SHA-256 1f0b2cfaecee1cbee621abb9c3c95811773ebfc17c374c4369fdc220bcf7d72d
Knowledge publish report sasedev-knowledge-0.0.1-publish.001-report.md = PASS
Knowledge start prompt prompts/002-V0_0_2_START_PROMPT.md
```
La source PGR explicitement réévaluée au démarrage de cette release est :
```text
PGR stable tag v0.1.3
PGR stable commit fe80dbbb737390a7ea99df27d979bd59ccfb86e3
PGR archive SHA-256 f3a7dffd5fe0fd3542e6b5207fda0b94bca2bd05bd5a54542d0a08d635a5d6ee
PGR publish report pgr-0.1.3-publish.001-report.md = PASS
```
Le passage de PGR `v0.1.1` à `v0.1.3` est traité comme adoption d'un projet existant via `230-ADOPTION`. Les artefacts historiques de `0.0.1` ne sont pas réécrits. `v0.1.3` constitue une provenance d'adoption, pas une dépendance normative vivante : les futurs démarrages Knowledge doivent partir des règles locales et ne consultent PGR que lorsqu'une nouvelle adoption est explicitement engagée.
`0.0.2-alpha.1` est `FAIL` pour défaut de structure du présent plan malgré des contrôles techniques propres. `alpha.1.fix.1` a corrigé uniquement ce cadrage documentaire et sa gate opérateur est `PASS`. `alpha.2` a été fermée par `alpha.2.fix.2` `PASS`. `alpha.3` a été fermée par `alpha.3.fix.1` `PASS`. `alpha.4` a été fermée par `alpha.4.fix.1` `PASS`, terminant l'adoption PGR comme migration ponctuelle. `alpha.5` est `PASS` et ferme le scope fonctionnel alpha avec `knowledge-app-lib` et le cas d'usage partagé `Changed` / `Unchanged`. `beta.1` est `PARTIAL`; `beta.1.fix.1` est `FAIL`; `beta.1.fix.2` est `PASS` et ferme la stabilisation beta. `rc.1` est `PASS` et valide la candidate complète. `rel.1` est la tranche courante de promotion mécanique pré-tag vers la stable `0.0.2`.
## Mission
`0.0.2` doit rendre réellement utilisable le chemin de lecture Git générique sans rendre une forge particulière obligatoire :
```text
source locale/remote
-> mirror contrôlé
-> refresh
-> résolution vers commit
-> snapshot/worktree déterministe
-> résultat de synchronisation changed/unchanged
```
La release doit transformer les contrats passifs de `0.0.1` en un backend Git de lecture testable, reproductible et remplaçable, tout en gardant Git autoritaire pour le contenu et la persistance métier hors de ce scope. Elle doit également terminer l'adoption PGR engagée au démarrage en rendant Knowledge autonome : règles applicables adaptées/possédées localement, PGR conservé comme provenance et non chargé automatiquement à chaque reprise.
## État acquis et questions ouvertes
Décisions déjà matérialisées par le cadrage `alpha.1` et non remises en cause par son `FAIL` documentaire :
- PGR `v0.1.3` est la source d'adoption explicitement retenue pour `0.0.2`, tandis que la baseline normative courante doit être possédée localement par Knowledge ;
- les changements PGR Java/Maven/Gradle/Android ne deviennent pas applicables à Knowledge ;
- le `DEFER` Tauri reste inchangé ;
- le backend initial utilise un processus `git` contrôlé via `std::process::Command`, sans shell ;
- `knowledge-git-lib` possède l'implémentation et la configuration physique Git ;
- la racine d'état Git généré est explicite et hors du dépôt source ;
- les mirrors vivent sous `repositories/<source-key>.git` ;
- les snapshots vivent sous `snapshots/<source-key>/<resolved-revision>` ;
- une source locale est clonée avec `--mirror --no-local` afin d'éviter une dépendance implicite aux objets du dépôt source ;
- une référence est résolue vers un commit réel avant matérialisation d'un snapshot ;
- la racine d'état reste exclusive à une instance/processus pour `0.0.2` ;
- la révision précédemment observée reste fournie par l'appelant tant que la persistance PostgreSQL est hors scope ;
- `gix` et `git2` restent des alternatives différées, pas des backends ajoutés par symétrie.
- `knowledge-app-lib` est matérialisée en `alpha.5` comme owner du cas d'usage partagé refresh / résolution / comparaison / snapshot.
Questions laissées ouvertes jusqu'à la tranche qui les consomme :
- politique de nettoyage/rétention des snapshots ;
- besoin réel de supporter submodules ou Git LFS dans cette release ;
- mécanisme concret de scheduler périodique, volontairement non requis pour prouver le cas d'usage de synchronisation ;
- prochaine mission/version après `0.0.2`, à décider uniquement lors de la revue d'horizon.
## Scope
`0.0.2` inclut :
- adoption PGR applicable au projet existant ;
- étude et choix documenté du backend Git initial ;
- matérialisation de `knowledge-git-lib` lorsqu'une responsabilité runtime réelle existe ;
- configuration explicite d'une source Git locale ou remote et d'une racine d'état ;
- création/inspection d'un mirror contrôlé ;
- refresh idempotent des refs utiles ;
- résolution d'une référence vers un commit ;
- matérialisation et validation d'un snapshot/worktree détaché adressé par révision ;
- cas d'usage de synchronisation distinguant `Changed` et `Unchanged` à partir de la révision précédente fournie par l'appelant ;
- tests unitaires et d'intégration avec repositories Git temporaires locaux ;
- validation de non-régression des contrats/domaines déjà stables ;
- réconciliation documentaire, candidate et fermeture stable de la release.
La création d'une crate supplémentaire d'orchestration n'appartient au scope que si une responsabilité partagée réelle est démontrée ; elle n'est pas imposée par le plan.
## Hors scope
`0.0.2` n'implémente pas :
- API Gitea/GitHub/GitLab ;
- webhooks de forge ;
- pull/merge requests ;
- branch/commit/rebase/merge/push de publication ;
- PostgreSQL et migrations ;
- persistance durable de l'état de synchronisation ;
- parsing final PGR/HOWTO/snippet ;
- moteur de recherche ;
- authentification ou administration ;
- worker réseau séparé ;
- cache Git partagé simultanément par plusieurs processus ;
- scheduler périodique concret sans besoin démontré ;
- backend secondaire `gix`/`git2` sans replanification motivée ;
- changement de rendu/UI Web.
## Stratégie de validation
Les gates suivent [`../validation/001-VALIDATION_MATRIX.md`](../validation/001-VALIDATION_MATRIX.md) et restent proportionnées au scope modifié.
Principes :
- `alpha.1` et son fix utilisent une gate documentaire/gouvernance, avec validation explicite de la structure du plan ;
- toute tranche modifiant Rust rejoue `cargo fmt`, les audits Rust applicables, `cargo metadata`, `cargo check`, Clippy strict et les tests ciblés/workspace nécessaires ;
- les propriétés Git fonctionnelles sont prouvées avec de vrais repositories temporaires locaux, pas seulement avec des mocks du runner ;
- aucun accès Internet ni aucune forge publique ne doit être nécessaire pour prouver le chemin Git générique ;
- les tests de source locale et remote locale doivent distinguer initialisation, refresh, résolution et snapshot ;
- les erreurs doivent être testées sans exposer de secret ou argument sensible ;
- la baseline Web est une non-régression : les gates npm/Vite et smokes HTTP sont rejoués lorsqu'une tranche peut les affecter et pendant la stabilisation ;
- aucun `PASS` assistant ne remplace le résultat opérateur ;
- un `FAIL`, `PARTIAL` ou `BLOCKED` est historisé avant correction.
## Trajectoire prévisionnelle
### alpha.1 — adoption PGR, étude et cadrage
Fermer les prérequis du prompt `002` avant toute implémentation Git fonctionnelle : adoption PGR, inventaire des contrats existants, étude comparative `git`/`gix`/`git2`, ownership de `knowledge-git-lib`, layout de l'état généré, plan et gate de release.
Le résultat opérateur initial est **FAIL** : les contrôles techniques transmis sont propres, mais le plan `0.0.2` ne respectait pas le patron de pilotage déjà établi par `0.0.1` (scope global absent, trajectoire hors section dédiée, niveaux de titres incorrects, déclencheurs de REPLAN et revue d'horizon absents).
`alpha.1.fix.1` corrige uniquement cette structure documentaire, ajoute la gate de non-régression correspondante et ne modifie aucune décision technique de l'étude Git. Aucun fichier sous `crates/` ou `Web/` n'est modifié dans `alpha.1` ou son fix.
La gate opérateur de `alpha.1.fix.1` est **PASS**.
### alpha.2 — contrats minimaux et backend process
Statut opérateur initial : **FAIL**.
`alpha.2.fix.1` corrige l'hygiène de fin de fichier, la portée de la gate Markdown, le formatage rustfmt et la construction interne de `GitSourceKey`, mais sa gate reste **FAIL** sur une unique violation `clippy::implicit_return` dans une closure de test.
`alpha.2.fix.2` corrige exclusivement ce retour implicite, sans modification du backend, de l'API, des dépendances ou du comportement Git. Sa gate opérateur est **PASS** et ferme `alpha.2`.
Le delta matérialise le premier backend réel sans encore prétendre couvrir tout le cycle de synchronisation. Les choix concrets de la tranche sont : configuration publique `GitBackendConfig`/`GitSourceConfig`, runner process strictement interne, erreurs publiques redacted par construction et `source-key` hexadécimale déterministe dérivée du `SourceId`.
Le scope reste :
- créer `knowledge-git-lib` avec responsabilité réelle ;
- introduire uniquement les types/configurations nécessaires aux sources et à la racine d'état ;
- encapsuler `std::process::Command` derrière un composant interne testable, sans shell ;
- construire les arguments Git comme arguments séparés ;
- valider les chemins et l'ownership de l'état généré ;
- implémenter l'initialisation et l'inspection minimale du mirror ;
- étendre `knowledge-git-api` uniquement si un besoin de contrat public est déjà démontré ;
- tester configuration, chemins, arguments, erreurs et redaction.
La tranche ne crée ni dépendance forge, ni scheduler, ni persistance.
Si la surface « configuration + runner + mirror » ne peut plus être validée proprement comme une seule responsabilité, REPLAN avant d'ajouter du scope.
### alpha.3 — refresh, résolution et snapshot réel
Statut initial : **FAIL**. `alpha.3.fix.1` : **PASS**. Tranche fermée.
La tranche a matérialisé et prouvé sur de vrais repositories temporaires :
- enregistrement explicite des configurations physiques dans `GitBackend` ;
- implémentation réelle de `knowledge-git-api::GitRepository` par `knowledge-git-lib` ;
- extension minimale du contrat avec `GitSnapshot` / `prepare_snapshot` ;
- refresh idempotent du mirror via `remote update --prune` ;
- résolution typée avec `rev-parse --verify --end-of-options ...^{commit}` ;
- worktree/snapshot détaché adressé par révision ;
- validation du `HEAD` et de l'état propre avant réutilisation ;
- changement de révision après ajout d'un commit source ;
- rejet d'une référence invalide et d'un snapshot sale.
Les tests fonctionnels passent, mais la gate complète échoue sur trois défauts de conformité : ligne vide terminale dans la matrice, formatage rustfmt d'un test `knowledge-git-api` et documentation de crate manquante dans le test d'intégration `git_read_path`.
`alpha.3.fix.1` corrige exclusivement ces défauts techniques et enregistre le présent REPLAN. Il ne modifie pas le comportement Git déjà prouvé.
La revue de tranche a déclenché un REPLAN explicite : l'autonomie normative après adoption PGR et l'organisation interne des crates doivent être traitées avant l'orchestration de synchronisation.
### alpha.4 — autonomie normative et structure interne des crates
Statut opérateur initial : **FAIL**. `alpha.4.fix.1` : **PASS**. Tranche fermée.
La matérialisation normative, les audits locaux, les façades crate-root, rustfmt et les métadonnées sont propres. La compilation échoue uniquement parce que `GitReference` et `GitRevision`, déplacés dans leurs modules propriétaires, tentent encore de construire leurs tuple structs privés via les réexports crate-root.
`alpha.4.fix.1` corrige exclusivement ces deux constructions avec `Self(...)`, sans modifier l'API publique, les owners locaux, les templates, les audits ni les comportements Git/sync.
Finaliser l'adoption PGR `v0.1.3` comme migration ponctuelle et rendre Knowledge autonome pour les sessions suivantes.
Scope :
- matérialiser six owners locaux : gouvernance, workflow, documentation, validation, Rust/Cargo et frontend Web, avec les contrats opérationnels réellement nécessaires (lifecycle/versioning, fixes/REPLAN, delta/history/handoff, rôles documentaires et catégories de preuve) ;
- produire `docs/architecture/003-PGR_ADOPTION_MAP.md` avec le mapping `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER` et les owners Knowledge réels ;
- conserver PGR `v0.1.3` uniquement comme provenance de l'adoption ;
- fournir des templates locaux de nouvelle session, plan de release, handoff opérateur et crate bibliothèque ;
- adopter localement les audits Markdown headers/liens/tables utiles au dépôt ;
- ajouter un audit Knowledge garantissant que les `src/lib.rs` des crates bibliothèque restent des façades ;
- réorganiser `knowledge-git-api` en modules `reference`, `revision`, `snapshot`, `repository` sans modifier son API crate-root ;
- réorganiser `knowledge-sync-api` en modules `signal` et `event_source` sans modifier son API crate-root ;
- auditer les autres crates et ne pas les découper davantage lorsqu'elles respectent déjà le contrat de façade ;
- conserver inchangée la sémantique Git prouvée par `alpha.3`.
Hors scope :
- aucune consultation d'une version PGR postérieure à `v0.1.3` ;
- aucune copie mécanique de tous les owners/skeletons/templates PGR ;
- aucun changement de comportement Git, forge, PostgreSQL, auth/admin ou Web runtime ;
- aucun `knowledge-app-lib` ni cas d'usage `Changed` / `Unchanged` avant `alpha.5`.
Gate attendue : audits Markdown locaux, audit d'autonomie, audits Rust incluant la façade crate-root, rustfmt/check/Clippy, tests ciblés des deux crates `-api` et `knowledge-git-lib`, puis tests workspace. `Web/` doit rester sans diff.
### alpha.5 — cas d'usage de synchronisation
La matérialisation de l'orchestration confirme un ownership application partagé : `knowledge-app-lib` devient justifiée et doit éviter que les trois launchers recomposent chacun le même flux.
Scope :
- créer `knowledge-app-lib` comme façade bibliothèque mince de cas d'usage partagés ;
- enchaîner refresh, résolution, comparaison avec `previous_revision` et snapshot ;
- retourner explicitement `RepositorySyncOutcome::Unchanged` ou `RepositorySyncOutcome::Changed` avec la révision observée ;
- préparer le snapshot uniquement lorsque la révision diffère de celle fournie par l'appelant ;
- consommer `RepositorySyncSignal` sans donner de sémantique autoritaire à sa cause ;
- prouver qu'une cause `Periodic` emprunte exactement le même cas d'usage qu'une cause explicite sans matérialiser de scheduler ;
- garder la persistance de dernière révision, PostgreSQL, worker réseau, forge et publication Git hors scope.
La gate Rust applique désormais explicitement `cargo fmt --all`, puis `cargo fmt --all -- --check`, avant les audits du code Rust. Cette normalisation précède les contrôles structurels afin que les audits observent l'état réellement formaté.
### beta.1 — réconciliation et revue d'horizon
Statut opérateur : **PARTIAL**.
La tranche n'ajoute aucune grande fonctionnalité. La réconciliation documentaire, les audits Rust/Git, la baseline Web, le manifest Vite et la revue d'horizon passent. La gate générée a toutefois omis les trois smokes HTTP alors que la stratégie de validation du présent plan les exige pendant la stabilisation.
`beta.1.fix.1` corrige le contrat de smoke et acquiert effectivement les trois smokes HTTP, mais reste `FAIL` parce que son audit Python de stabilisation contient une chaîne mal quotée et que `001-VALIDATION_MATRIX.md` introduit une ligne vide supplémentaire à EOF. `beta.1.fix.2` corrige uniquement ces deux défauts de gate, rend durable la règle d'un unique LF terminal et retire les modifications persistantes d'options shell des gates interactives. Aucun code produit, contrat métier, package ou scope fonctionnel n'est modifié.
La réconciliation doit :
- inventorier les neuf packages et contrats effectivement matérialisés ;
- confirmer `knowledge-git-lib` comme backend Git et `knowledge-app-lib` comme owner du cas d'usage partagé ;
- vérifier les `DEFER` et hors-scope encore valides ;
- corriger les formulations durables héritées de `0.0.1` qui utilisent encore des numéros d'alpha non qualifiés ;
- confirmer l'absence de dépendance forge/PostgreSQL/auth/admin et d'écriture Git ;
- rejouer la gate Rust/Git et la non-régression Web utile ;
- produire une étude de réconciliation durable ;
- réaliser la revue d'horizon sans réserver de numéro futur par défaut.
Résultat de revue proposé et soumis à la gate beta : aucune prochaine mission n'est suffisamment décidée pour engager une version postérieure à `0.0.2`. Les domaines futurs restent non numérotés.
### rc.1 — candidate stable
Statut opérateur : **PASS**.
`beta.1.fix.2` est `PASS`. Le scope fonctionnel de `0.0.2` est gelé et la candidate doit exécuter la gate complète de release sans introduire de changement produit.
La RC doit notamment :
- ne pas introduire de nouvelle fonctionnalité ;
- confirmer la cohérence des histories et deltas de `0.0.2` ;
- confirmer la reproductibilité des tests Git locaux ;
- rejouer Cargo/Clippy/tests, audits applicables, baseline Web, manifest Vite et smokes HTTP ;
- confirmer l'absence de dépendance forge/PostgreSQL/auth/admin hors scope ;
- confirmer l'état de la revue d'horizon et du handoff suivant s'il existe.
Un défaut fonctionnel important découvert en RC impose un retour de phase ou un REPLAN ; il ne doit pas être absorbé silencieusement dans la promotion stable.
### rel.1 — fermeture stable
Promotion mécanique de la candidate validée vers `0.0.2` :
- porter `VERSION = 0.0.2` et la version workspace identique ;
- stabiliser `README.md`, `ROADMAP.md`, `CHANGELOG.md`, l'index et le présent plan ;
- conserver tous les histories `FAIL`/`PASS` réels ;
- préparer un prompt de reprise uniquement si la revue d'horizon a réellement engagé une prochaine release ;
- rejouer la gate pré-tag complète ;
- produire le history `rel.1` uniquement après résultats opérateur ;
- après `PASS`, committer/pousser le produit stable, créer/pousser le tag annoté `v0.0.2`, télécharger l'archive du tag et produire séparément `publish.001`.
La publication post-tag ne peut pas être anticipée dans le delta `rel.1`.
## Déclencheurs de REPLAN
REPLAN obligatoire si :
- une tranche accumule plusieurs responsabilités non prévues qui ne peuvent plus être validées proprement ensemble ;
- le backend process ne peut pas satisfaire les propriétés de sécurité/déterminisme sans introduire un shell ou une dépendance plus large ;
- une opération Git fondamentale exige finalement une API de forge ;
- les sources réelles exigent submodules, Git LFS ou une autre capacité qui change substantiellement le modèle de snapshot ;
- le modèle de `source-key` ou de chemins ne peut pas garantir isolation et absence de collision sans modifier les contrats publics ;
- le partage de la racine d'état entre plusieurs processus devient nécessaire et impose verrouillage/coordination ;
- l'extension de `knowledge-git-api` change de manière substantielle les contrats validés en `0.0.1` ;
- l'orchestration de synchronisation crée une duplication réelle qui impose un nouvel owner commun non prévu ;
- des mesures concrètes rendent le backend process inadéquat et justifient de réévaluer `gix` ou `git2` ;
- trois fixes ou plus apparaissent sur une même tranche ;
- une réévaluation PGR explicitement engagée révèle un changement substantiel d'un contrat déjà adopté par Knowledge ;
- un prérequis externe invalide la mission de `0.0.2`.
Un REPLAN modifie le présent plan de manière explicite ; il ne doit pas être masqué par un simple élargissement opportuniste d'un delta.
## Clôture et transition
- documentation durable : réconciliée à chaque tranche qui change un contrat, puis revue globalement en `beta.1` ;
- `CHANGELOG.md` : consolidé en candidate/stable sans catalogue de fixes internes inutile ;
- `ROADMAP.md` : revue d'horizon en `beta.1`, sans création automatique d'une prochaine version ;
- prompt/handoff suivant : créé uniquement si une prochaine mission est réellement décidée et fondé sur la baseline normative locale Knowledge ; aucune consultation PGR automatique n'y est ajoutée ;
- fermeture du plan : `rel.1` ou son fix établissant la stable ;
- publication : commit produit `0.0.2`, tag stable `v0.0.2`, archive du tag puis rapport post-tag séparé ;
- toute tranche suit `delta -> gate opérateur -> history réel -> commit/push -> tranche suivante ou fix`.
## Revue d'horizon
État courant : **réalisée ; stabilisation beta et candidate `rc.1` fermées `PASS`; `rel.1` en promotion stable pré-tag**.
Résultat : aucune version postérieure à `0.0.2` n'est réservée. Les domaines futurs restent non numérotés parce qu'aucune mission suivante ne possède encore un scope et un ordre suffisamment décidés.
Les besoins différés confirmés incluent notamment : PostgreSQL/migrations, parsing/index, propositions/admin/auth, adapter de forge, écriture/publication Git, scheduler concret, submodules/LFS, politique de rétention des snapshots et partage multi-processus de la racine Git.
Une future mission pourra être engagée depuis la stable `0.0.2` lorsqu'un besoin produit ou d'exploitation choisira explicitement l'un de ces axes. Elle devra repartir de la baseline locale Knowledge et ne consultera PGR que si une réévaluation PGR est elle-même explicitement dans le scope.

View File

@@ -0,0 +1,301 @@
<!-- file: docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md -->
<!-- version: 12 -->
# Plan 0.0.3 — écriture Git contrôlée et push gardé
## Statut et sources
Plan historique de la stable `0.0.3`, ouverte depuis la stable Knowledge `v0.0.2` publiée et vérifiée.
Sources autoritaires de reprise :
```text
Knowledge tag stable v0.0.2
Knowledge commit stable 183d354a67bec6d2d512c4898f5189759a6120b0
Knowledge archive SHA-256 0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce
Knowledge publish report sasedev-knowledge-0.0.2-publish.001-report.md = PASS
Knowledge start prompt prompts/003-V0_0_3_START_PROMPT.md
```
PGR `v0.1.3` reste une provenance historique de l'adoption fermée pendant `0.0.2`. Aucune version PGR plus récente n'est une source de cette release et aucune réévaluation PGR n'est engagée.
`0.0.3-alpha.1` et `alpha.1.fix.1` sont conservées `PARTIAL` avec leurs preuves réelles. `alpha.1.fix.2` a complété la gate documentaire et technique en `PASS`. `alpha.2` a matérialisé le contrat d'écriture provider-neutral en `PASS`. `alpha.3` reste historisée `FAIL` pour son défaut de test `E0369`; `alpha.3.fix.1` a corrigé uniquement cette assertion en `PASS`. `alpha.4` a fermé le scope fonctionnel alpha en `PASS` avec push gardé, création de branche sous attente d'absence et conflit concurrent sans écrasement. `beta.1` a réconcilié l'état acquis, préparé le changelog candidat, rejoué la non-régression Rust/Web et effectué la revue d'horizon produit en `PASS`. `rc.1` a rejoué toutes les preuves produit/runtime mais reste historisée `FAIL` pour une assertion lexicale de statut ; `rc.1.fix.1` a corrigé uniquement cette preuve en `PASS`. `rel.1` est la tranche courante de promotion mécanique pré-tag vers la stable `0.0.3`.
## Mission
`0.0.3` doit étendre le backend Git générique de Knowledge avec une capacité d'écriture contrôlée, indépendante d'une forge, capable de produire une révision candidate puis de mettre à jour une branche distante uniquement si son état précédent correspond encore à l'attente de l'appelant.
Le chemin cible est :
```text
source Git configurée
-> base commit explicite
-> worktree d'écriture isolé
-> mutations de fichiers confinées
-> commit avec identité explicite
-> révision candidate
-> push gardé vers refs/heads/...
-> succès ou conflit explicite
```
La release fournit une primitive Git réutilisable aux futurs workflows de proposition/publication ; elle n'implémente pas encore ces workflows produit.
## État acquis et questions ouvertes
Acquis par `0.0.2` :
- Git reste la source autoritaire du contenu publié ;
- `knowledge-git-api::GitRepository` possède le contrat provider-neutral de lecture ;
- `knowledge-git-lib` possède la configuration physique, les mirrors, refresh, résolution et snapshots ;
- les opérations Git sont lancées sans shell, avec prompts interactifs désactivés et erreurs publiques redacted ;
- les snapshots de lecture sont adressés par révision et vérifiés avant réutilisation ;
- `knowledge-app-lib` possède uniquement le cas d'usage de synchronisation `Changed` / `Unchanged` ;
- Gitea, PostgreSQL, auth/admin et workflow complet de proposition restent différés.
Décisions prises par l'étude `006` :
- conserver le processus `git` contrôlé ;
- utiliser un worktree d'écriture isolé plutôt qu'une nouvelle implémentation Git ou un moteur plumbing complet ;
- introduire un contrat d'écriture distinct de `GitRepository` afin de préserver les consommateurs lecture seule ;
- limiter `0.0.3` aux branches `refs/heads/...` ;
- utiliser une attente explicite sur l'état distant pour empêcher l'écrasement concurrent ;
- traiter une divergence comme conflit récupérable, sans rebase/merge automatique ;
- ne créer aucune nouvelle crate tant qu'un owner distinct n'est pas démontré.
Décisions d'implémentation désormais résolues dans les alphas :
- mutations publiques provider-neutral matérialisées dans `knowledge-git-api` ;
- worktree d'écriture backend déterministe sous `write-worktrees/<source-key>/candidate` ;
- candidats retenus sous `refs/knowledge/candidates/<revision>` ;
- conflits distants représentés par `GitPushOutcome::Conflict`, séparés des erreurs process ;
- garde distante matérialisée par observation `ls-remote` puis `push --force-with-lease` exact ;
- `remote.origin.mirror=true` neutralisé uniquement sur le push ciblé afin de ne jamais publier toutes les refs internes du mirror.
## Scope
La release couvre :
- contrat passif d'écriture Git dans `knowledge-git-api`, séparé du contrat de lecture ;
- création/modification/suppression de fichiers par chemins relatifs validés ;
- worktree d'écriture sous la racine d'état contrôlée, séparé des snapshots de lecture ;
- commit à partir d'une base `GitRevision` explicite ;
- identité auteur/committer et message fournis explicitement ;
- neutralisation des hooks et de la configuration Git ambiante pertinente ;
- résultat de révision candidate explicite ;
- push vers une branche générique avec garde sur l'état distant attendu ;
- conflit explicite et non destructif en cas de divergence ;
- tests unitaires et d'intégration locaux couvrant sécurité de chemin, commit, push et concurrence ;
- documentation durable des invariants réellement matérialisés.
## Hors scope
`0.0.3` n'introduit pas :
- API Gitea/GitHub/GitLab, webhook, pull/merge request ou release de forge ;
- rebase/merge automatique ;
- workflow complet de proposition/revue, commentaires ou audit applicatif ;
- PostgreSQL, migrations ou persistance de dernière révision ;
- auth/admin ou modèle rôles/capabilities ;
- route HTTP d'écriture ou UI d'administration ;
- modification du rendu public ou du package Web ;
- signature de commits ;
- Git LFS/submodules ;
- partage concurrent de la racine d'état Git entre plusieurs processus ;
- formats finaux HOWTO/snippet ;
- scheduler/job de publication.
## Stratégie de validation
Les alphas fonctionnelles privilégient les preuves Git locales et ciblées :
```text
cargo fmt --all
cargo fmt --all -- --check
-> audits Rust structurels applicables
-> cargo metadata / cargo check
-> clippy strict
-> tests ciblés knowledge-git-api / knowledge-git-lib
-> tests d'intégration avec repositories temporaires locaux/bare
-> tests workspace lorsque la frontière publique ou le workspace change
```
Les tests d'écriture ne nécessitent ni Internet ni forge. Ils créent leur propre repository source/remote temporaire et fournissent explicitement l'identité de commit.
`Web/` reste hors scope alpha ; npm/Vite ne sont pas rejoués par défaut sans diff Web. La phase beta/RC pourra imposer la non-régression runtime globale conformément à l'owner de validation.
La gate de `alpha.1` reste une gate de cadrage : elle valide baseline, documentation, version, audits et non-régression du workspace existant, sans prétendre tester une implémentation d'écriture encore absente.
## Trajectoire prévisionnelle
### alpha.1 — baseline, étude et cadrage
- vérifier la stable `v0.0.2` et son rapport post-tag ;
- confirmer l'autonomie normative Knowledge sans nouvelle consultation PGR ;
- réconcilier les `DEFER` avec la prochaine mission ;
- produire l'étude `006` ;
- engager `0.0.3` et établir un premier horizon au-delà d'elle ;
- mettre à jour les owners/architecture vivants nécessaires ;
- établir le présent plan ;
- créer exceptionnellement `prompts/003-V0_0_3_START_PROMPT.md` comme rattrapage, puisque `0.0.2` avait été fermée sans prochaine mission décidée ;
- ne modifier aucun code sous `crates/` ni `Web/`.
### alpha.1.fix.1 — correction du cycle documentaire
- conserver `alpha.1` comme `PARTIAL` sans réécrire ses preuves ;
- retirer du changelog l'entrée candidate écrite prématurément ;
- faire de la roadmap une vue de la release en cours plus un horizon prévisionnel borné ;
- codifier que le changelog est rédigé vers la stabilisation à partir des changements réellement acquis ;
- codifier que le prompt de `N+1` est normalement préparé pendant les dernières phases de `N`, après revue d'horizon ;
- garder `prompts/003-V0_0_3_START_PROMPT.md` comme rattrapage historique valide ;
- ne modifier aucun code sous `crates/` ni `Web/`.
### alpha.1.fix.2 — complétion de la preuve opérateur
- conserver `alpha.1.fix.1` comme `PARTIAL` sans réécrire les preuves observées ;
- ne modifier aucune décision fonctionnelle du cadrage ;
- rejouer la gate utile et terminer explicitement les assertions documentaires, l'hygiène texte, l'invariance `crates/` / `Web/`, la politique `Cargo.lock` et le staging ;
- ne modifier aucun code sous `crates/` ni `Web/` ;
- autoriser `alpha.2` uniquement après un `PASS` complet.
### alpha.2 — contrat d'écriture provider-neutral
- étendre `knowledge-git-api` avec un contrat distinct du `GitRepository` de lecture ;
- matérialiser uniquement les types nécessaires aux mutations, identité de commit, branche cible, état attendu et résultat ;
- valider strictement les chemins relatifs et les branches `refs/heads/...` ;
- préserver les implémentations/fakes lecture seule sans leur imposer des méthodes d'écriture ;
- ne pas effectuer encore de push distant réel si le découpage de la tranche devient disproportionné.
### alpha.3 — worktree d'écriture et commit candidat
- partir de `alpha.2 = PASS` historisé et committé ;
- matérialiser dans `knowledge-git-lib` un worktree d'écriture isolé dérivé d'une base résolue ;
- appliquer create/update/delete sous confinement et refuser les traversées par symlink ;
- neutraliser la configuration Git système/globale pertinente et les hooks ;
- créer un commit avec identité auteur/committer explicite ;
- vérifier que le candidat a exactement la base demandée comme parent ;
- retenir le candidat sous une référence backend interne sans mettre à jour de branche distante ;
- nettoyer l'état temporaire de manière déterministe sans supprimer silencieusement un worktree préexistant.
### alpha.3.fix.1 — correction de l'assertion du test candidat
- conserver `alpha.3 = FAIL` avec la preuve exacte de compilation `E0369` ;
- ne modifier aucun code produit de `knowledge-git-lib` ni aucun contrat public ;
- remplacer uniquement l'assertion invalide sur `Result<String, std::io::Error>` par une vérification du cas `Ok("first\n")` qui n'exige pas `PartialEq` sur `std::io::Error` ;
- rejouer Clippy, le test `git_write_candidate` et les tests workspace ;
- autoriser `alpha.4` uniquement après `PASS` complet du fix.
### alpha.4 — push gardé et concurrence
- partir de `alpha.3.fix.1 = PASS` historisé et committé ;
- observer l'état de la branche distante via le remote générique `origin` ;
- exiger que la révision candidate soit encore retenue par le backend ;
- pousser la révision candidate uniquement si la cible correspond à l'attente explicite ;
- utiliser `--force-with-lease` comme garde compare-and-exchange autoritaire ;
- gérer création de branche lorsque l'absence est attendue ;
- réobserver après rejet afin de produire un conflit récupérable en cas de divergence concurrente ;
- implémenter `GitRepositoryWriter` sur `GitBackend` maintenant que create + push sont disponibles ;
- prouver l'absence d'écrasement par tests d'intégration avec remote bare local ;
- fermer le scope fonctionnel alpha sans ajouter workflow de proposition ou forge.
### beta.1 — réconciliation et revue d'horizon
- partir de `alpha.4 = PASS` historisé et committé ;
- réconcilier contrats, implémentation et documentation durable sans modifier `crates/` ni `Web/` ;
- préparer `CHANGELOG.md` en état `0.0.3 (candidate)` à partir des capacités réellement acquises ;
- vérifier les `DEFER` encore valides et reclasser la prochaine dépendance structurante ;
- retenir `0.0.4` comme fondation PostgreSQL partagée, sans préchoisir driver/ORM avant son étude de démarrage ;
- étendre l'horizon jusqu'aux phases de modèles/projections, templates publics, templates admin, intégration Actix/Maud et workflow de gestion/proposition/publication des trois sites ;
- produire l'étude durable `007-V0_0_3_GIT_WRITE_RECONCILIATION.md` ;
- préparer `prompts/004-V0_0_4_START_PROMPT.md` pendant cette stabilisation, sans inventer commit/tag/SHA postérieurs de `v0.0.3` ;
- rejouer la non-régression Rust et Web utile à la stabilisation ;
- n'introduire aucune fonctionnalité PostgreSQL, Web, auth, forge ou proposition dans `0.0.3`.
### rc.1 — candidate stable
- geler le scope ;
- rejouer la gate complète applicable à la release ;
- vérifier reproductibilité des tests Git d'écriture locaux, intégrité des deltas/histories, Cargo/Clippy/tests et non-régression Web/runtime prévue par la phase ;
- n'introduire aucune nouvelle fonctionnalité.
### rc.1.fix.1 — correction de la vérification des statuts historiques
- conserver `rc.1 = FAIL` avec la preuve exacte de l'assertion lexicale trop stricte ;
- ne réécrire aucun history antérieur, en particulier `alpha.1` dont le statut historique est écrit `` `PARTIAL` `` ;
- normaliser uniquement la lecture du statut lors des gates afin d'accepter la présentation Markdown existante ;
- ne modifier aucun fichier sous `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/` ou `prompts/` ;
- rejouer les contrôles documentaires, Cargo/Clippy/tests et la cohérence RC nécessaires à ce fix ;
- autoriser `rel.1` uniquement après `PASS` complet du fix.
### rel.1 — promotion stable pré-tag
- promouvoir mécaniquement `VERSION` et le workspace vers `0.0.3` ;
- finaliser le changelog à partir du scope réellement acquis ;
- fermer roadmap/plan sans nouveau scope et vérifier que le handoff de la prochaine release décidée est déjà présent ;
- rejouer la gate stable prévue ;
- produire le history observé avant commit produit/tag ;
- ne fabriquer aucun rapport post-tag avant publication réelle.
### rel.1.fix.1 — complétion de la preuve de clôture stable
- conserver `rel.1 = PARTIAL` avec toutes les preuves produit/runtime réellement acquises ;
- ne modifier aucun code produit, frontend, changelog, roadmap, architecture, étude, règle ou prompt ;
- compléter explicitement l'audit de composition du workspace à partir du JSON `cargo metadata` déjà prévu par la gate ;
- compléter explicitement l'audit d'hygiène texte sur les documents gouvernés ;
- réutiliser les preuves Cargo/Clippy/tests, Web et smokes HTTP `PASS` de `rel.1` puisqu'aucun fichier concerné n'est modifié ;
- autoriser le premier commit stable `0.0.3` uniquement après `PASS` complet de ce fix.
## Déclencheurs de REPLAN
REPLAN avant de poursuivre si :
- le contrat d'écriture exige de casser le contrat de lecture existant au lieu de rester séparé ;
- le push gardé ne peut pas garantir l'absence d'écrasement avec une attente explicite ;
- l'implémentation nécessite une forge spécifique ;
- un besoin de rebase/merge automatique devient nécessaire pour fermer la mission ;
- l'écriture révèle une responsabilité de persistance PostgreSQL indispensable à la correction du backend lui-même ;
- le cycle de vie des worktrees exige un owner/processus distinct ;
- le partage multi-processus de la racine d'état devient nécessaire ;
- la stratégie process Git actuelle devient inadéquate pour une raison mesurée ;
- une tranche atteint `.fix.3`, conformément à la gouvernance locale.
La conclusion du REPLAN doit utiliser `CONTINUE`, `SPLIT`, `CONTINUE-NEXT-SESSION` ou `ABANDON`.
## Clôture et transition
La release est fermable lorsque :
- l'écriture produit une révision candidate à partir d'une base explicite ;
- les mutations restent confinées au worktree contrôlé ;
- le commit n'utilise ni hooks ni identité Git ambiante ;
- la mise à jour distante est gardée contre une révision attendue ou une absence attendue ;
- une divergence produit un conflit sans écrasement ;
- les tests locaux réels couvrent les chemins critiques ;
- les règles/architecture décrivent l'état effectivement obtenu ;
- beta et RC ont fermé les non-régressions prévues.
Le `PARTIAL` de `alpha.1` a autorisé `0.0.3-alpha.1.fix.1`. Le premier fix est resté `PARTIAL`, puis `alpha.1.fix.2` a fermé la preuve en `PASS`. `alpha.2` a matérialisé le contrat d'écriture provider-neutral en `PASS`. `alpha.3` est conservée `FAIL` à cause de l'assertion de test `E0369`; `alpha.3.fix.1` a corrigé uniquement le test en `PASS`. `alpha.4` a ensuite fermé le scope fonctionnel alpha en `PASS`. `beta.1` a stabilisé et réconcilié cet état en `PASS`, sans nouveau code produit. `rc.1` a confirmé toutes les preuves produit/runtime mais reste `FAIL` à cause d'une assertion lexicale trop stricte sur le statut historique `alpha.1`. `rc.1.fix.1` a corrigé uniquement cette preuve en `PASS`. `rel.1` a acquis toutes les preuves produit/runtime de la promotion stable mais reste `PARTIAL` parce que deux contrôles annoncés — composition exacte du workspace et hygiène texte — ne figurent pas dans le transcript. `rel.1.fix.1` ferme uniquement ces deux preuves avant le premier commit stable réellement `PASS`.
## Revue d'horizon
La revue `0.0.3-beta.1` remplace l'horizon initial trop centré sur les seules briques backend. La prochaine mission engagée par la promotion stable est :
```text
0.0.4 fondation PostgreSQL partagée et migrations
```
L'horizon produit devient ensuite :
```text
0.0.5 modèles de contenu et projections PGR/HOWTO/snippet
0.0.6 templates publics des trois sites, pages de garde incluses
0.0.7 templates d'administration des trois sites
0.0.8 routes publiques Actix Web + adaptation Maud
0.0.9 auth/autorisation + routes admin Actix Web + adaptation Maud
0.0.10 gestion/proposition/revue/publication Git + PostgreSQL
```
Ces versions après `0.0.4` restent des horizons révisables. Elles rendent cependant explicite l'objectif produit : trois sites distincts PGR, HOWTOs et snippets, chacun avec lecture publique et gestion, partageant Git comme vérité éditoriale, PostgreSQL comme état/projection applicative et un pipeline de templates Web validés avant adaptation runtime en Maud.
Recherche avancée, adapters de forge, webhooks forge-only, scheduler, administration transverse, opérations multi-domaines, Git LFS/submodules et partage multi-processus restent non numérotés tant qu'un besoin réel ne les engage pas.
`prompts/004-V0_0_4_START_PROMPT.md` a été préparé pendant `beta.1` conformément au cycle normal de handoff. La stable `0.0.3` le conserve : au prochain démarrage il exigera l'archive taggée réelle `v0.0.3` et son rapport post-tag, sans avoir inventé à l'avance le commit stable ou le SHA-256.

25
docs/rules/000-README.md Normal file
View File

@@ -0,0 +1,25 @@
<!-- file: docs/rules/000-README.md -->
<!-- version: 1 -->
# Règles locales Knowledge
## Rôle
Ce répertoire contient les owners normatifs durables nécessaires au développement de `sasedev-knowledge` après l'adoption ponctuelle de PGR `v0.1.3`.
Ces documents sont autonomes. Leur lecture ne nécessite pas d'ouvrir le dépôt PGR. Les numéros d'owner conservent une continuité de provenance lorsque cela reste utile, mais le texte local Knowledge est désormais autoritaire pour ce projet.
## Owners actifs
- [`200-GOVERNANCE.md`](200-GOVERNANCE.md) — autorité de baseline, décisions, adoption externe explicite et autonomie normative.
- [`210-WORKFLOW.md`](210-WORKFLOW.md) — plan, delta, gate opérateur, history, commit/push, fix et handoff.
- [`300-DOCUMENTATION.md`](300-DOCUMENTATION.md) — rôles documentaires, headers, plans, prompts et historique.
- [`400-VALIDATION.md`](400-VALIDATION.md) — preuves proportionnées, statuts et autorité opérateur.
- [`510-RUST_CARGO.md`](510-RUST_CARGO.md) — workspace Rust/Cargo strict, façade crate-root, tests et audits.
- [`530-WEB_FRONTEND.md`](530-WEB_FRONTEND.md) — TypeScript, npm, Vite, Sass, Bootstrap et contrat template/Maud.
Le mapping d'adoption qui explique pourquoi ces owners existent et quels owners PGR n'ont pas été matérialisés localement est conservé dans [`../architecture/003-PGR_ADOPTION_MAP.md`](../architecture/003-PGR_ADOPTION_MAP.md).
## Priorité
[`../../RULES.md`](../../RULES.md) reste le point d'entrée normatif du dépôt et possède les décisions produit spécifiques. Les owners de ce répertoire détaillent les contrats transverses. Une règle plus spécifique au produit ou à une architecture concrète prime dans son scope lorsqu'elle est explicitement documentée.

View File

@@ -0,0 +1,122 @@
<!-- file: docs/rules/200-GOVERNANCE.md -->
<!-- version: 1 -->
# Gouvernance locale Knowledge
## Principe
La gouvernance de Knowledge sert la qualité, la reprise et la réduction du risque. Elle reste proportionnée au changement : une règle ou une gate doit apporter une preuve, protéger un contrat ou rendre une décision durable ; elle ne doit pas exister uniquement pour ajouter de la cérémonie.
## Baseline autoritaire
Une version ou tranche Knowledge part des artefacts Knowledge réellement autoritaires : archive/tag ou workspace établi, `VERSION`, règles locales, architecture, plan actif, deltas/histories et preuves opérateur disponibles.
Une conversation antérieure, une copie non vérifiée ou une version plus récente d'un référentiel externe ne remplace jamais cette baseline.
## Autonomie normative et provenance PGR
Knowledge est autonome après adoption. PGR `v0.1.3` est la provenance de la réconciliation effectuée pendant `0.0.2`, pas une dépendance normative vivante.
Une nouvelle version, session ou tranche Knowledge MUST NOT rechercher ou charger automatiquement une nouvelle stable PGR. Une comparaison PGR ne peut commencer que si une réévaluation/adoption PGR est explicitement inscrite au scope.
Lors d'une telle réévaluation, les décisions sont classifiées `ADOPT / KEEP-LOCAL / OVERRIDE / DEFER`, puis les changements retenus sont adaptés et matérialisés dans Knowledge avant fermeture de l'adoption.
## Autorité locale et priorité
L'ordre d'autorité normal est :
```text
contrat spécifique de tranche/release établi
-> RULES.md et owners locaux docs/rules/
-> architecture, plan et validation locaux
-> provenance externe uniquement pour expliquer une adoption explicitement engagée
```
Un owner externe n'est jamais appliqué implicitement parce qu'il porte un numéro identique ou une version plus récente.
## Version canonique
`VERSION` contient exactement l'identité SemVer courante de la release Knowledge, sans préfixe `v`. `[workspace.package].version` est un miroir exact de `VERSION` pour le workspace Rust.
Les autres numéros restent indépendants lorsque leur sémantique l'exige : révision documentaire, lockfile/package, format ou compteur technique. Une égalité de version n'est jamais imposée uniquement parce que deux champs contiennent un numéro.
Le lifecycle Knowledge utilise :
```text
X.Y.Z-alpha.N
X.Y.Z-alpha.N.fix.M
X.Y.Z-beta.N
X.Y.Z-beta.N.fix.M
X.Y.Z-rc.N
X.Y.Z-rc.N.fix.M
X.Y.Z
```
`N` et `M` commencent à `1`. Le tag stable est `vX.Y.Z`. `rel.N` nomme une tranche de clôture et n'est pas une version produit SemVer ; sa forme canonique n'utilise pas de zéro de padding (`rel.1`).
## Rôle des phases
### Alpha
L'alpha porte cadrage, implémentation et changements de scope encore attendus. `alpha.1` sert de gate de cadrage de la release : baseline, règles, mission/plan, décisions ouvertes, risques, stratégie de validation et dimensionnement des tranches doivent être vérifiés avant l'implémentation lourde.
Une durée de l'ordre de 15 à 30 minutes peut aider au découpage, mais n'est ni une deadline ni un quota.
### Beta
La beta sert principalement à stabiliser et réconcilier un scope déjà établi. Une expansion fonctionnelle importante exige un REPLAN explicite.
### Release candidate
La RC gèle le scope sauf corrections nécessaires à la validation. Elle porte la gate complète de candidate. Un changement fonctionnel important découvert en RC retourne vers une phase adaptée ou déclenche un REPLAN ; il n'est pas masqué dans un simple fix.
### Stable
La promotion vers `X.Y.Z` doit être proche d'une opération mécanique après une RC propre, tout en exécutant la gate spécifique aux métadonnées/documents/artefacts de promotion.
Pour la clôture stable :
```text
rel.1 FAIL / PARTIAL / BLOCKED -> commit X.Y.Z-rel.1
rel.1.fix.1 FAIL / PARTIAL / BLOCKED -> commit X.Y.Z-rel.1.fix.1
première clôture réellement PASS -> commit X.Y.Z
```
Le commit `X.Y.Z` inclut le history qui établit le `PASS` stable. Le tag `vX.Y.Z` est créé seulement après ce commit. La vérification de l'archive/tag publiée est une preuve post-tag distincte.
## Correctifs de tranche
Un `.fix.M` corrige uniquement la tranche immédiatement concernée. Il conserve son scope fonctionnel et peut corriger code, configuration, documentation, validation ou packaging nécessaires à cet objectif.
Un fix ne doit pas introduire silencieusement une nouvelle fonctionnalité ou un nouveau chantier. Son compteur repart à `1` pour chaque tranche.
À partir de `.fix.3`, une revue du diagnostic et du plan est obligatoire afin de décider explicitement si la poursuite du fix reste appropriée ou si un REPLAN est préférable.
## REPLAN
Un REPLAN est une décision de pilotage, pas un suffixe SemVer. Il s'applique lorsque le plan ne décrit plus correctement la réalité : dépendance inattendue, responsabilité transverse, architecture, risque, volume ou ordre des tranches significativement modifié.
La conclusion d'un REPLAN doit être explicite :
```text
CONTINUE
SPLIT
CONTINUE-NEXT-SESSION
ABANDON
```
Une coupure de conversation ou de session ne crée jamais à elle seule une nouvelle version ou une nouvelle tranche. Le même état continue tant que le scope reste valide.
## Abandon et supersession
Une version ou prerelease commencée n'est pas forcée vers stable pour préserver une séquence esthétique. En cas d'abandon ou supersession, l'état et la raison sont documentés, les preuves et décisions utiles sont conservées, et la version déjà consommée reste dans l'historique.
## Décisions différées
Une décision `DEFER` reste indécise jusqu'à son déclencheur réel. Elle n'est pas remplacée par une valeur arbitraire issue d'un exemple, d'un autre projet ou d'un référentiel externe.
Les décisions différées structurantes restent dans le profil/architecture Knowledge et ne sont reprises que par un scope explicite ou un REPLAN.
## Immutabilité historique
Les artefacts historiques restent historiques. Une nouvelle règle s'applique à partir de son adoption et ne réécrit pas deltas, histories, prompts ou décisions anciennes uniquement pour simuler une conformité rétroactive.

127
docs/rules/210-WORKFLOW.md Normal file
View File

@@ -0,0 +1,127 @@
<!-- file: docs/rules/210-WORKFLOW.md -->
<!-- version: 2 -->
# Workflow local Knowledge
## Unité de travail
`VERSION` est l'identité canonique de la tranche courante. Une tranche est livrée par un delta, validée dans l'environnement opérateur, puis clôturée par un history qui enregistre l'observation réelle.
Le cycle normal est :
```text
delta
-> application opérateur
-> gate applicable
-> résultat PASS / FAIL / PARTIAL / BLOCKED
-> history observé
-> application du history
-> git add / diff --cached --check / status
-> commit
-> push
-> tranche suivante ou fix
```
Le delta suivant ne doit pas être appliqué avant matérialisation Git du history précédent.
## Chemins et archives canoniques
Les contrats de tranche vivent sous :
```text
deltas/<X.Y.Z>/<tranche>.md
history/<X.Y.Z>/<tranche>.md
```
Les archives échangées sont séparées :
```text
sasedev-knowledge-<X.Y.Z>-<tranche>.delta.zip
sasedev-knowledge-<X.Y.Z>-<tranche>.history.zip
```
Un history n'est jamais inclus dans le delta qu'il valide. Le delta suivant peut être livré dans la même réponse que le history précédent lorsque sa préparation est déjà déterminée, mais son application reste postérieure au commit/push du history.
## Contrat d'un delta
Un delta contient des fichiers complets à ajouter ou remplacer. Les suppressions éventuelles sont explicites via un manifeste de suppression lorsque le mécanisme d'application l'exige ; elles ne reposent pas sur une instruction manuelle ambiguë.
Le document de delta indique au minimum :
- baseline requise et version obtenue ;
- mission et scope ;
- fichiers/contrats réellement concernés ;
- hors-scope et décisions différées pertinentes ;
- gate opérateur reproductible ;
- transition autorisée après chaque statut attendu.
Un delta doit être applicable sans reconstruire à la main des patchs depuis une conversation.
## Contrat d'un history
Aucun history `PASS` n'est créé avant réception des preuves opérateur réelles. Une prévalidation de génération peut détecter des erreurs, mais ne constitue jamais le résultat autoritaire de la tranche.
Le history enregistre au minimum :
- tranche et baseline observées ;
- statut `PASS / FAIL / PARTIAL / BLOCKED` ;
- preuves réellement exécutées et leurs résultats utiles ;
- anomalies/limites pertinentes ;
- décision de transition.
Un history appliqué et committé devient immuable. Un futur fix crée son propre history ; il ne remplace pas le résultat précédent.
## Plans et REPLAN
Une release multi-responsabilités possède un plan versionné. Le plan décrit mission, état acquis, scope, hors-scope, validation, trajectoire prévisionnelle, déclencheurs de REPLAN, clôture/transition et revue d'horizon lorsque pertinente.
Les tranches de la trajectoire sont des sous-sections `###` de `## Trajectoire prévisionnelle`.
Un REPLAN modifie uniquement les parties devenues fausses ou insuffisantes. Les deltas/histories gardent la chronologie détaillée ; le plan reste un document de pilotage vivant et non un journal de terminal.
## Fixes
Un `FAIL`, `PARTIAL` ou `BLOCKED` est conservé. Une correction bornée au même scope utilise `.fix.N`. Le history en échec est appliqué, committé et poussé avant le fix.
Un fix ne doit pas absorber silencieusement une nouvelle fonctionnalité. Si le diagnostic révèle une responsabilité transverse ou une mission différente, le plan est replanifié explicitement.
## Handoff opérateur
Après livraison d'un history, les commandes de matérialisation Git sont fournies automatiquement. Pour `origin/master` établi dans ce dépôt, une tranche non stable suit normalement :
```bash
git add -A
git diff --cached --check
git status --short
git commit -m "<VERSION>"
git push origin master
git status --short
```
Les commandes ne sont pas différées à une réponse ultérieure. Lorsque le delta suivant est prêt, son application est indiquée après ce bloc afin que l'ordre soit sans ambiguïté.
## Prompts et changement de session
Un prompt de démarrage versionné décrit la baseline attendue, les sources requises, la mission et les vérifications nécessaires pour ouvrir une release. Il ne complète pas silencieusement des données manquantes par un état supposé d'une conversation ou d'un workspace non vérifié.
Le cycle normal prépare le prompt de la release `N+1` pendant les dernières phases de `N`, après la revue d'horizon qui a suffisamment défini la prochaine mission et avant la clôture stable de `N`. Comme le commit/tag/rapport post-tag définitifs de `N` n'existent pas encore à ce moment, le prompt versionné ne doit pas inventer leur SHA ou leur résultat : il exige que l'introduction de la session suivante fournisse l'archive taggée et le rapport post-tag réels et qu'ils soient vérifiés avant développement.
Si une stable historique a été fermée sans prompt de la release suivante parce que la mission n'était pas encore décidée, le prompt peut exceptionnellement être créé au démarrage réel de cette release ; cette situation est un rattrapage et non le cycle normal.
Une coupure de session au milieu d'une tranche ne change pas `VERSION`. Si nécessaire, un handoff local décrit l'état exact, les décisions acquises, les preuves déjà obtenues et ce qui reste à exécuter.
Le pattern normal de nouvelle session est [`../templates/001-NEW_SESSION_PROMPT.md`](../templates/001-NEW_SESSION_PROMPT.md).
## Stable et publication
La première clôture `rel.N[/fix.M]` réellement `PASS` est committée avec le message produit `X.Y.Z`; les clôtures échouées conservent leur identifiant de tranche dans le message de commit conformément à la gouvernance locale.
Le tag stable `vX.Y.Z` est créé seulement après le commit stable. L'archive téléchargée depuis ce tag est la baseline de publication autoritaire ; l'absence de `.git` dans cette archive est normale.
Le rapport post-tag `sasedev-knowledge-X.Y.Z-publish.001-report.md` est une preuve distincte. Il vérifie le tag/archive réellement publiés et ne doit pas être préfabriqué comme preuve du delta `rel.1`.
Le prompt d'une release suivante n'est préparé que si sa mission est suffisamment décidée par la revue d'horizon. Lorsqu'elle l'est, ce prompt fait partie des artefacts de fin de la release courante afin que la stable publiée contienne déjà son handoff vers la suite. Les éléments post-tag encore inconnus restent à vérifier au démarrage suivant plutôt qu'à être inventés.
## Immutabilité des artefacts historiques
Les deltas, histories et prompts déjà committés ne sont pas réécrits pour adopter une convention plus récente, corriger une esthétique ou masquer un échec passé. Une correction de contrat durable s'applique aux artefacts futurs et est documentée chez son owner local.

View File

@@ -0,0 +1,95 @@
<!-- file: docs/rules/300-DOCUMENTATION.md -->
<!-- version: 2 -->
# Documentation locale Knowledge
## Langue et identités
Les noms techniques, chemins, fichiers, crates, identifiants et code sont en anglais. La documentation normative et explicative principale est en français.
Chaque fichier Markdown gouverné par Knowledge commence par :
```text
<!-- file: <chemin relatif exact> -->
<!-- version: <entier positif> -->
```
La révision documentaire augmente lorsqu'un contenu réel du fichier change ; elle n'est pas un compteur global de release et ne doit pas être synchronisée mécaniquement avec `VERSION`.
## Rôle des documents racine
`README.md` est une entrée de projet : identité, état utile, démarrage et liens vers les owners. Il ne devient pas une copie complète des règles ou de l'architecture.
`RULES.md` est l'entrée normative locale et renvoie vers les owners détaillés sous `docs/rules/`.
`ROADMAP.md` décrit la release en cours et un horizon borné des prochaines releases. Il distingue explicitement une mission engagée d'une version d'horizon encore révisable ; l'horizon sert à ordonner les dépendances et à préparer la suite sans transformer chaque intention en engagement irrévocable. Il ne sert ni de journal quotidien ni de catalogue exhaustif des prereleases.
`CHANGELOG.md` synthétise les changements réellement acquis d'une release. Il n'est normalement pas rédigé au démarrage ou pendant le cadrage alpha à partir d'intentions : une entrée candidate est préparée vers les phases de stabilisation, lorsque l'implémentation et les histories permettent de décrire ce qui existe effectivement, puis finalisée avant la promotion stable. Les fixes et essais alpha internes ne sont pas transformés en catalogue exhaustif lorsque leur détail appartient déjà aux deltas/histories.
`docs/000-README.md` indexe la documentation durable et fournit le chemin de lecture principal.
## Familles documentaires
Knowledge distingue :
```text
RULES / docs/rules contrat normatif local
docs/architecture architecture et décisions structurantes
docs/studies analyse avant décision ou réconciliation
docs/plans pilotage vivant d'une release multi-tranches
docs/validation matrices, critères et contrats de preuve
docs/templates patterns locaux réutilisables
prompts handoff versionné d'ouverture d'une release
deltas contrat d'une tranche à appliquer
history résultat réellement observé d'une tranche
```
Un delta/history ne remplace pas la documentation durable. Une décision structurante acquise finit chez son owner durable lorsqu'elle doit gouverner la suite.
## Documents vivants et historiques
`RULES.md`, `docs/rules/`, l'architecture, la roadmap et un plan actif sont des documents vivants : ils évoluent lorsque leur contrat actuel change.
`deltas/`, `history/` et les prompts de releases passées sont historiques après commit. Ils ne sont pas réécrits uniquement pour se conformer à une nouvelle convention apparue plus tard.
Une étude terminée peut rester historique tout en étant référencée depuis la décision durable qu'elle a permis d'établir.
## Plans
Un plan actif contient au minimum, lorsque pertinent : statut/sources, mission, état acquis/questions ouvertes, scope, hors-scope, stratégie de validation, trajectoire prévisionnelle, déclencheurs de REPLAN, clôture/transition et revue d'horizon.
Les phases prévisionnelles se trouvent sous `## Trajectoire prévisionnelle` et utilisent des headings `###`.
Le plan est mis à jour lorsqu'une hypothèse de pilotage devient fausse, pas pour recopier chaque sortie de commande. Les preuves détaillées appartiennent aux histories.
## Revue d'horizon
Lorsqu'une release approche de sa stabilisation, une revue d'horizon explicite réévalue la release suivante et, autant que les dépendances sont suffisamment connues, au moins les deux releases d'horizon qui la suivent. Elle peut engager la prochaine mission, réordonner ou scinder un horizon, maintenir un domaine non numéroté ou reclasser un `DEFER`.
Une version marquée comme horizon n'est pas encore une promesse de scope : son numéro et sa mission peuvent changer à la revue suivante. La roadmap doit néanmoins donner assez de visibilité pour que la release en cours soit développée dans une trajectoire produit cohérente plutôt qu'en isolation.
## Prompts de nouvelle session
Un prompt normal Knowledge repart de la stable Knowledge, de son rapport post-tag lorsqu'il existe, puis du prompt versionné local. Il vérifie les artefacts Knowledge locaux avant de préparer la tranche suivante.
Le prompt de la release suivante est normalement rédigé pendant les dernières phases de la release courante, après sa revue d'horizon. Il fait partie du handoff livré par la stable courante. Les identifiants post-tag qui n'existent pas encore lors de sa rédaction ne sont pas inventés : la session suivante les reçoit dans son texte d'introduction et les vérifie contre l'archive et le rapport réellement publiés.
Il MUST NOT demander de rechercher ou charger PGR ou un autre référentiel externe, sauf lorsque cette release contient explicitement une réévaluation/adoption de ce référentiel.
Le pattern local est [`../templates/001-NEW_SESSION_PROMPT.md`](../templates/001-NEW_SESSION_PROMPT.md).
## Templates
Les templates locaux sont des patterns adaptables, pas des documents normatifs à recopier aveuglément. L'owner normatif reste la règle locale correspondante ; un template sert à réduire les omissions lors de la création d'un nouvel artefact.
## Liens, tables et audits
Les liens Markdown locaux doivent pointer vers des cibles existantes. Les tables source doivent rester syntaxiquement cohérentes. Les arbres générés (`.git`, `node_modules`, `target`, `builds`) n'appartiennent pas au corpus documentaire gouverné.
Les audits Markdown locaux sont read-only :
```text
scripts/audit_markdown_headers.py
scripts/audit_markdown_links.py
scripts/audit_markdown_tables.py
```

View File

@@ -0,0 +1,123 @@
<!-- file: docs/rules/400-VALIDATION.md -->
<!-- version: 4 -->
# Validation locale Knowledge
## Principe
Une tranche sélectionne ses preuves à partir du scope modifié, des contrats potentiellement invalidés et du risque réel. La validation est proportionnée : elle ne rejoue pas mécaniquement toutes les commandes disponibles lorsque celles-ci n'apportent aucune preuve nouvelle.
La matrice canonique est [`../validation/001-VALIDATION_MATRIX.md`](../validation/001-VALIDATION_MATRIX.md).
## Statuts
Les statuts reconnus sont :
```text
PASS toutes les preuves requises ont réussi
FAIL au moins une preuve requise a échoué
PARTIAL seules certaines preuves requises ont été acquises
BLOCKED un prérequis externe empêche d'exécuter une preuve requise
```
Un statut incomplet ne devient jamais implicitement `PASS`.
## Autorité de la gate
La gate opérateur exécutée dans l'environnement autoritaire possède le statut de tranche. Une prévalidation par assistant, génération locale ou CI supplémentaire peut réduire le risque de livraison mais ne remplace pas cette gate.
Une commande qui échoue dans la gate reste un `FAIL` même si des tests exécutés ensuite passent. Les preuves positives sont conservées dans le diagnostic mais ne neutralisent pas l'échec requis.
## Catégories de preuve
Les gates peuvent combiner, selon le scope :
```text
FORMAT état produit par le formatter officiel
AUDIT contrat structurel read-only non possédé par l'outil officiel
CHECK compilation/typecheck/manifest sans production nécessaire d'artefact
LINT politique statique stricte
TEST comportement ciblé ou workspace
BUILD production d'un artefact réellement nécessaire
SMOKE preuve d'intégration/processus/frontière runtime
PACKAGE layout ou archive de livraison
PUBLISH tag/archive/remote effectivement publiés
```
Une catégorie n'est pas ajoutée pour remplir un quota ; elle doit protéger une propriété affectée par la tranche.
## Niveau de preuve
Une propriété est vérifiée au niveau le plus fiable et le moins redondant disponible :
```text
compilateur / langage
-> outil officiel ou linter
-> manifest / build system
-> audit structurel read-only
-> test comportemental
-> smoke d'intégration lorsque l'environnement le nécessite
```
Une règle qualitative qui exige du jugement humain reste une revue plutôt qu'une regex fragile créée uniquement pour automatiser.
## Tests
Les tests ciblés sont privilégiés pour le scope directement modifié. Les tests workspace sont utilisés lorsqu'un contrat transverse, un changement de structure ou une phase de consolidation peut invalider plusieurs composants.
Les tests unitaires doivent rester indépendants d'Internet et de services externes non contrôlés. Un test d'intégration réel peut utiliser des ressources locales isolées et temporaires lorsqu'il prouve une frontière que des fakes ne démontrent pas correctement.
Un test ne modifie pas l'API produit uniquement pour faciliter son accès à des détails internes.
## Audits
Les audits locaux sont read-only et monotâches autant que raisonnable. Ils contrôlent une propriété du dépôt que le compilateur, rustfmt, Clippy, Cargo, TypeScript, npm ou Vite ne possède pas déjà de manière fiable.
Un audit ne doit pas analyser les caches et dépendances générés comme s'ils faisaient partie du source gouverné. Les scripts Markdown locaux ignorent notamment `.git`, `node_modules`, `target` et `builds`.
Lorsqu'un audit lexical rencontre un cas que son modèle ne sait pas interpréter sans heuristique fragile, la propriété revient à l'outil officiel ou à une revue humaine plutôt qu'à un faux positif permanent.
## Ordre normal
Pour une tranche Rust transverse, le formatter officiel normalise d'abord les sources. `cargo fmt --all` est une étape mutante volontaire et doit être exécutée avant tout audit du code Rust ; `cargo fmt --all -- --check` confirme immédiatement l'état obtenu.
La séquence habituelle est :
```text
cargo fmt --all
-> cargo fmt --all -- --check
-> audits Rust structurels applicables
-> cargo metadata / cargo check utiles
-> clippy strict
-> tests ciblés
-> tests workspace si justifiés
-> smokes/builds/packages seulement si le scope les invalide
-> git diff --cached --check / status
```
Les audits Markdown ou autres contrôles documentaires peuvent être exécutés avant ou après le formatage Rust puisqu'ils ne portent pas sur les sources Rust.
Une gate fournie pour exécution dans le shell interactif de l'opérateur ne doit pas modifier durablement les options du shell avec `set -e`, `set -u`, `set -o pipefail` ou une combinaison équivalente. La gate doit laisser chaque commande produire son résultat observable sans risquer de terminer la session terminal de l'opérateur.
Pour une tranche Web, les preuves npm/typecheck/build sont ajoutées lorsque `Web/` ou son contrat d'intégration est touché. Une tranche documentaire ou Rust sans diff Web ne rejoue pas automatiquement le frontend.
Une phase de stabilisation globale explicitement prévue par le plan (`beta`, `rc` ou clôture stable) peut imposer une preuve de non-régression runtime même sans diff direct sous `Web/` ou dans les launchers. Lorsqu'un plan exige un smoke de stabilisation, cette preuve est obligatoire et ne peut pas être omise au motif que le code runtime n'a pas changé dans la tranche.
## Régression et dette externe
Un warning provenant d'une dépendance tierce n'est pas automatiquement un `FAIL` si la gate locale ne le traite pas comme tel et si l'outil termine avec succès. Il doit toutefois être reclassé si une mise à jour de toolchain le transforme en rupture réelle ou si la release cible exige zéro warning sur cette frontière.
## Traçabilité
## Hygiène des fichiers texte
Les fichiers texte gouvernés doivent utiliser LF et se terminer par exactement un unique LF. Un suffixe `\n\n` est une ligne vide supplémentaire à EOF et doit être rejeté par la validation lorsqu'il est introduit par la tranche.
La validation d'hygiène doit donc distinguer :
```text
...dernière ligne\n valide
...dernière ligne\n\n invalide
```
Chaque delta liste sa gate exécutable et la matrice conserve le contrat durable. Le history enregistre les résultats réellement observés ; il ne résume pas une prévalidation comme si elle avait été exécutée par l'opérateur.

View File

@@ -0,0 +1,81 @@
<!-- file: docs/rules/510-RUST_CARGO.md -->
<!-- version: 2 -->
# Rust / Cargo local Knowledge
## Toolchain et workspace
Knowledge utilise Rust stable, édition 2024 et un workspace Cargo à train de release unique. `[workspace.package].version` suit `VERSION` et les crates membres héritent de la version commune.
Aucun `rust-toolchain.toml` n'est ajouté sans besoin réel de reproductibilité ou de compatibilité. Les artefacts Cargo sont dirigés hors de l'arborescence source par la configuration locale lorsque possible.
`Cargo.lock` est généré localement, ignoré et non versionné pour ce workspace. Cette politique ne s'applique pas au lockfile npm.
## Nomenclature et responsabilités
Les crates utilisent le préfixe `knowledge-`. Les suffixes portent un contrat :
```text
-api frontière passive de contrats réutilisables/remplaçables
-lib implémentation ou fonctionnalité réutilisable
-server launcher/processus mince
```
Une crate n'est pas créée par symétrie. Une `-api` ne contient ni runtime, ni logging imposé, ni implémentation de provider. Un launcher ne duplique pas une logique qui possède un owner bibliothèque commun.
## Façade crate-root
Pour toute crate bibliothèque Knowledge, `src/lib.rs` est une façade de crate. Il contient la documentation de crate, les déclarations de modules, les réexports publics/internes nécessaires et, lorsque pertinent, le rattachement conditionnel des tests.
Les `struct`, `enum`, `trait`, fonctions, implémentations et autres définitions de comportement appartiennent à des modules nommés selon leur responsabilité. Une petite crate n'est pas artificiellement éclatée en nombreux fichiers : un seul module de responsabilité peut suffire, mais les définitions ne s'accumulent pas dans `lib.rs`.
Les crates `-api` nomment leurs modules par le contrat réel (`reference`, `revision`, `snapshot`, `repository`, `signal`, etc.) plutôt que par un générique `api.rs` sans ownership.
Les consommateurs externes utilisent la façade `owner_crate::Item`. Les références intra-crate vers des items partagés passent par `crate::Item` afin de garder une identité stable indépendante du module propriétaire.
`scripts/audit_rust_crate_root_facades.py` contrôle que les crate-roots de bibliothèque restent des façades sans définitions top-level de production.
## Safety, erreurs et contrôle de flux
`unsafe` est interdit par défaut. Les lints workspace stricts restent autoritaires.
Le code de production ne doit pas utiliser `unwrap`, `expect`, `panic!` ou l'opérateur `?` lorsque les lints locaux les interdisent. Les erreurs récupérables sont propagées explicitement avec le type d'erreur possédé par la couche concernée.
Les retours explicites exigés par la politique Clippy locale sont conservés, y compris dans les closures lorsque le lint `implicit_return` s'applique.
## Imports, modules et réexports
Les `use` non exportés sont réservés aux imports réellement nécessaires, notamment les traits. Les modules de production précèdent les modules de tests conditionnels et suivent un ordre déterministe lorsqu'aucune sémantique ne l'interdit.
Les réexports crate-root restent groupés par visibilité. La façade publique doit être complète pour les items partagés et les modules propriétaires restent privés sauf besoin architectural explicite.
## Tests
Les tests unitaires résident sous `unit_tests/` à la racine de chaque crate et sont rattachés depuis le module source correspondant. Les tests d'intégration Cargo résident sous `tests/` et consomment l'API publique.
Les tests ne rendent pas l'API produit plus visible uniquement pour accéder à des détails internes.
## Formatage et audits
Le formatter est appliqué, pas seulement interrogé : `cargo fmt --all` précède obligatoirement les audits du code Rust, puis `cargo fmt --all -- --check` vérifie immédiatement le résultat.
La séquence normale d'un changement Rust pertinent inclut :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 -B scripts/audit_rust_test_layout.py crates
python3 -B scripts/audit_rust_blank_lines.py crates
python3 -B scripts/audit_rust_item_spacing.py crates
python3 -B scripts/audit_rust_module_order.py crates
python3 -B scripts/audit_rust_trait_import_blocks.py crates
python3 -B scripts/audit_rust_reexport_blocks.py crates
python3 -B scripts/audit_rust_crate_root_facades.py crates
python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Les tests ciblés sont sélectionnés selon le scope. `cargo test --workspace --all-targets --all-features` est utilisé pour les changements transverses et les phases de consolidation.
L'audit mécanique des blocs `const` n'est pas sélectionné globalement par Knowledge : l'ordre de constantes peut être sémantique. Il ne sera ajouté localement que si un besoin réel et un corpus approprié le justifient.

View File

@@ -0,0 +1,57 @@
<!-- file: docs/rules/530-WEB_FRONTEND.md -->
<!-- version: 1 -->
# Frontend Web local Knowledge
## Scope
Cet owner regroupe les contrats Web réellement utilisés par Knowledge : TypeScript, npm, Vite, Sass, Bootstrap et l'articulation entre le template statique de référence et le rendu runtime Maud.
Il remplace, pour le développement courant de Knowledge, la nécessité de relire plusieurs profils Web externes séparés.
## Package et dépendances
Le package de référence est `Web/knowledge-template-lab/` et utilise npm. `package.json` possède les scripts explicites `typecheck`, `dev`, `build` et `preview` nécessaires au workflow courant.
`package-lock.json` est versionné. `node_modules/`, caches Vite et sorties de build restent générés et hors Git.
Les versions de dépendances sont décidées dans le package Knowledge ; aucune version externe n'est adoptée automatiquement parce qu'un template ou référentiel plus récent existe.
## TypeScript
Le frontend utilise TypeScript strict. Le typecheck doit couvrir le code applicatif et la configuration Vite selon les `tsconfig` locaux.
Une dépendance de types n'est ajoutée que lorsqu'elle est réellement nécessaire à l'IDE, au compilateur ou au package consommé.
## Vite
`vite.config.ts` est le propriétaire de `root`, `base`, cache, dev server, preview, outputs et manifest. Les caches et builds sont dirigés vers les outputs Knowledge hors source.
Le build de production doit générer un manifest machine-readable et conserver l'entrée stable `src/main.ts`. Les noms d'assets hashés ne sont pas codés en dur dans le backend Rust.
## Sass et Bootstrap
Sass est la chaîne de styles sélectionnée et Bootstrap est un choix local Knowledge. Les warnings de dépréciation provenant d'une dépendance tierce sont suivis comme dette/compatibilité mais ne deviennent bloquants que si la gate locale l'exige ou s'ils annoncent une rupture effective de la toolchain retenue.
## Template statique et Maud
Le template sous `Web/knowledge-template-lab/` sert de harness de conception et de validation. Il ne constitue pas une seconde implémentation runtime.
Le markup de production est rendu par `knowledge-web-lib` avec Maud. L'adaptation depuis le template doit préserver les contrats utiles de structure, classes, attributs et assets.
En développement, `KNOWLEDGE_VITE_DEV_ORIGIN` fournit l'origin Vite. En production, `KNOWLEDGE_VITE_MANIFEST_PATH` désigne le manifest et `KNOWLEDGE_ASSET_BASE_PATH` peut remplacer le préfixe public.
## Validation
Lorsqu'un changement touche le package Web ou son contrat d'intégration, la preuve normale est :
```bash
(
cd Web/knowledge-template-lab
npm ci
npm run typecheck
npm run build
)
```
Les smokes Actix/Maud pertinents sont ajoutés lorsqu'un changement touche réellement le runtime ou la résolution d'assets. Une tranche exclusivement documentaire ou Rust sans diff Web ne rejoue pas automatiquement le build frontend si aucune preuve n'est invalidée.

View File

@@ -0,0 +1,245 @@
<!-- file: docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md -->
<!-- version: 1 -->
# Étude 0.0.2 — implémentation Git générique
## Objet
Cette étude ferme la décision technique exigée avant `0.0.2-alpha.1` : choisir le mécanisme qui implémentera le premier backend Git provider-neutral de Knowledge sans introduire de dépendance à une forge.
Le besoin réel est limité au chemin de lecture :
```text
source Git locale ou remote
-> copie locale contrôlée
-> refresh idempotent
-> résolution d'une référence vers un commit
-> snapshot/worktree de lecture déterministe
```
L'écriture Git, les pull/merge requests, les webhooks de forge, PostgreSQL, le parsing final et la publication restent hors scope.
## Contraintes déjà acquises
Les contrats de `0.0.1` restent autoritaires :
- `knowledge-domain-lib` possède l'identité logique `SourceId` et la famille de contenu ;
- `knowledge-git-api` possède le contrat provider-neutral `GitRepository` ;
- `knowledge-sync-api` possède les signaux non autoritaires `RepositorySyncSignal` ;
- le domaine ne connaît ni Gitea, ni GitHub, ni GitLab ;
- une requête publique ne déclenche jamais de fetch distant ;
- les tests de la voie générique doivent fonctionner avec des repositories temporaires locaux ;
- les erreurs doivent rester récupérables et aucune donnée sensible ne doit être journalisée.
## Sources techniques consultées
Consultation effectuée le `2026-09-28` :
- Rust `std::process::Command` : <https://doc.rust-lang.org/std/process/struct.Command.html> ;
- Git `clone` : <https://git-scm.com/docs/git-clone> ;
- Git `worktree` : <https://git-scm.com/docs/git-worktree> ;
- Git `rev-parse` : <https://git-scm.com/docs/git-rev-parse> ;
- `gix` `0.88.0` : <https://docs.rs/gix/0.88.0/gix/> et <https://docs.rs/crate/gix/0.88.0/features> ;
- `git2` `0.21.0` : <https://docs.rs/crate/git2/0.21.0>.
Ces sources éclairent le choix technique ; elles ne remplacent pas les tests réels de Knowledge.
## Option A — processus `git` contrôlé
### Capacités
Le client Git officiel couvre directement le besoin :
- `git clone --mirror` produit un repository bare qui mappe l'ensemble des refs et configure leur mise à jour depuis le remote ;
- `--no-local` permet d'éviter l'optimisation locale par hardlinks lorsqu'une source est un chemin local ;
- `git remote update --prune` rafraîchit le mirror sans créer une logique de forge ;
- `git rev-parse --verify --end-of-options <ref>^{commit}` vérifie qu'une référence fournie résout effectivement un commit et évite qu'elle soit interprétée comme une option ;
- `git worktree add --detach <path> <revision>` matérialise un arbre de lecture associé exactement à la révision résolue.
### Sécurité du processus
Rust `std::process::Command` passe les arguments directement au programme sans shell. Le backend peut donc imposer :
- `Command::new(...)` vers un exécutable Git réel, jamais `sh -c`, `bash -c`, `cmd.exe` ou un fichier `.bat` ;
- un argument par `arg`/`args`, sans concaténation de commande ;
- `--end-of-options` ou `--` lorsque la commande Git le permet ;
- `GIT_TERMINAL_PROMPT=0` pour empêcher une attente interactive d'un serveur ;
- un `current_dir` et des chemins appartenant au backend ;
- retrait des variables d'environnement qui peuvent rediriger implicitement un repository (`GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`, `GIT_OBJECT_DIRECTORY`, `GIT_ALTERNATE_OBJECT_DIRECTORIES`) ;
- désactivation des hooks via un `core.hooksPath` vide contrôlé pour les opérations pouvant déclencher un hook ;
- source locale/remote provenant uniquement de configuration de déploiement privilégiée, jamais d'un paramètre de requête publique ;
- rejet des syntaxes de remote-helper exécutables telles que `ext::` et absence de transport arbitraire découvert depuis une entrée utilisateur ;
- absence de credentials dans les logs et dans les erreurs publiques.
Les mécanismes de credentials autorisés restent une responsabilité de déploiement : SSH agent, credential helper ou autre mécanisme Git préconfiguré. Les secrets ne sont pas copiés dans la configuration versionnée de Knowledge.
### Portabilité et exploitation
Cette option ajoute un prérequis runtime explicite : un exécutable `git` compatible doit être présent. Elle évite en revanche une nouvelle pile Git native dans Cargo et utilise le comportement du client Git réellement déployé.
Le backend demeure synchrone au niveau du contrat actuel. Les appels processus sont bloquants et devront être isolés par la couche runtime lorsqu'ils sont déclenchés depuis Actix ; cette isolation ne justifie pas de rendre les crates de contrat async.
### Testabilité
Les tests d'intégration peuvent créer un repository temporaire local, y produire plusieurs commits puis exercer exactement la même implémentation que la production sans réseau ni forge publique.
Une prévalidation locale de cette étude a confirmé avec Git `2.47.3` la séquence :
```text
clone --mirror --no-local
resolve refs/heads/main^{commit}
remote update --prune
resolve nouvelle révision
worktree add --detach
HEAD du snapshot = révision résolue
worktree propre
```
Cette prévalidation assistant ne constitue pas une gate opérateur.
## Option B — `gix`
`gix` `0.88.0` fournit une abstraction Rust riche de repository Git. Sa feature matrix couvre notamment révisions, credentials, mutation de worktree et clients réseau bloquants ou async selon configuration.
Avantages :
- API Rust et erreurs typées ;
- pas de shell ni de dépendance à un exécutable Git pour les opérations couvertes ;
- contrôle fin des features ;
- bon candidat si Knowledge doit plus tard intégrer profondément des objets Git dans son processus.
Coûts pour `0.0.2` :
- surface de features et API sensiblement plus large que le besoin actuel ;
- choix supplémentaires pour le transport réseau et son modèle bloquant/async ;
- davantage de code d'adaptation à maintenir pour reproduire et tester les détails de comportement attendus du client Git ;
- aucune nécessité actuelle ne compense ce coût puisque le produit ne parse pas encore les objets Git lui-même.
`gix` reste donc une alternative future légitime, pas une option rejetée définitivement.
## Option C — `git2` / libgit2
`git2` `0.21.0` expose des bindings Rust vers libgit2. Le support réseau n'est pas activé par défaut ; HTTPS et SSH ajoutent des features et leurs dépendances associées. La crate dépend de `libgit2-sys` et peut construire ou lier libgit2 ; SSH/HTTPS peuvent également introduire libssh2/OpenSSL selon le choix de features.
Avantages :
- API Rust mature et erreurs structurées ;
- opérations locales, clone et fetch disponibles ;
- pas d'exécutable Git requis à l'exécution lorsque toute la pile nécessaire est liée.
Coûts pour Knowledge :
- dépendances natives supplémentaires et matrice de transport plus lourde ;
- comportement libgit2 à valider séparément du client Git déployé ;
- gain insuffisant pour une release qui n'a besoin que d'une matérialisation Git de lecture.
Cette option n'est pas retenue pour `0.0.2`.
## Option D — combinaison CLI + crate Git
Une combinaison pourrait utiliser le CLI pour clone/fetch et une crate pour lire les objets ou préparer les worktrees.
Elle n'apporte aujourd'hui aucune propriété nécessaire qui ne soit déjà couverte par le CLI. Elle doublerait au contraire les modèles d'erreur, les surfaces de compatibilité et les tests de cohérence.
Décision : ne pas introduire deux backends avant qu'un besoin concret le démontre.
## Comparaison
| Critère | Processus `git` contrôlé | `gix` | `git2` |
|--------------------------------|---------------------------------------|-------------------------------------|------------------------------------------|
| Fidélité au client Git déployé | directe | élevée mais à valider par opération | libgit2, comportement distinct à valider |
| Source locale | oui | oui | oui |
| Remote générique | oui | oui avec features réseau | oui avec features réseau |
| mirror/fetch | natif et direct | disponible via API/features | disponible via libgit2 |
| worktree de lecture | commande native | API/features de mutation | API libgit2 à composer |
| Erreurs structurées | wrapper Knowledge nécessaire | natives Rust | natives Rust |
| Injection shell | évitée avec `Command` direct | sans shell | sans shell |
| Dépendance runtime externe | exécutable Git | aucune pour les chemins couverts | libgit2 et pile native liée/vendored |
| Async | opérations bloquantes à isoler | modes bloquant/async selon features | principalement synchrone |
| Tests locaux reproductibles | simples avec repositories temporaires | possibles | possibles |
| Coût d'intégration `0.0.2` | faible à moyen | moyen à élevé | moyen à élevé |
## Décision
`0.0.2` retient **un processus `git` contrôlé** comme première implémentation de `knowledge-git-api`.
La décision est locale à Knowledge et ne devient pas une règle PGR générale.
Le backend sera matérialisé dans :
```text
crates/knowledge-git-lib/
```
Cette crate possédera :
- la configuration physique d'une source Git locale ou remote ;
- la racine d'état Git généré ;
- l'exécution contrôlée de Git ;
- le mirror local ;
- refresh/résolution ;
- la préparation et validation d'un snapshot de lecture.
`knowledge-domain-lib` conserve l'identité logique. `knowledge-git-api` conserve le contrat. La composition applicative associera un `SourceId` à une configuration concrète sans faire remonter les chemins ou URLs dans le domaine.
## Racine d'état et layout retenus
Les copies Git générées ne résident jamais dans le dépôt source `sasedev-knowledge` ni dans un dépôt éditorial.
Le backend reçoit une racine d'état explicite, sans défaut implicite dans le workspace :
```text
<git-state-root>/
hooks-disabled/
repositories/
<source-key>.git/
snapshots/
<source-key>/
<resolved-revision>/
```
La future composition runtime pourra alimenter cette racine via une configuration de déploiement telle que `KNOWLEDGE_GIT_STATE_ROOT`, mais `knowledge-git-lib` reçoit un `PathBuf` et ne dépend pas directement d'une variable d'environnement.
Règles :
- `source-key` est dérivé de manière sûre par l'implémentation ; ni `SourceId` brut ni URL remote ne sont interpolés comme chemin ;
- la racine d'état appartient exclusivement à une instance de backend/processus dans `0.0.2` ; le partage concurrent entre plusieurs processus reste hors contrat tant qu'un mécanisme de verrouillage inter-processus n'est pas matérialisé ;
- un snapshot est adressé par la révision réellement résolue par le backend ;
- un snapshot existant n'est réutilisé qu'après vérification que son `HEAD` correspond à cette révision et que le worktree est propre ; une divergence est une erreur, pas une suppression silencieuse ;
- les credentials ne sont jamais dérivés dans les noms de chemins.
## Politique de source locale et remote
Une source locale et une source remote convergent vers le même mirror contrôlé.
Pour une source locale, l'initialisation utilisera `clone --mirror --no-local` afin d'éviter de rendre la copie contrôlée dépendante des hardlinks/optimisations du repository source.
Après initialisation, le refresh utilise la configuration du remote du mirror et une mise à jour avec pruning. Le backend ne dépend donc pas d'une API de forge.
## Résolution de révision
Une référence configurée reste opaque dans `knowledge-git-api`, mais l'implémentation doit produire une révision qui désigne un commit réel :
```text
git --git-dir=<mirror> rev-parse --verify --end-of-options <reference>^{commit}
```
La sortie validée devient `GitRevision`. Aucun nom de branche ou tag non résolu n'est utilisé comme identité de snapshot.
## Ownership de synchronisation
`RepositoryEventSource` reste inchangé : il ne fait qu'émettre des signaux.
Le cas d'usage partagé qui enchaîne refresh, résolution, comparaison de la précédente révision et préparation du snapshot appartient à la couche application. La candidate existante `knowledge-app-lib` devient donc justifiée lorsque cette orchestration est implémentée ; elle ne sera pas créée en `alpha.1` uniquement comme placeholder.
En l'absence de PostgreSQL dans `0.0.2`, la dernière révision observée est fournie par l'appelant au cas d'usage puis retournée dans son résultat. Cette release ne crée pas de pseudo-base locale cachée.
La cause `Periodic` reste supportée par le contrat de signal, mais un scheduler concret est différé tant que son owner runtime/lifecycle n'est pas démontré. Une future source périodique devra appeler le même cas d'usage que la synchronisation explicite.
## Conséquences pour les prochaines tranches
`alpha.1` ne modifie aucun contrat Rust fonctionnel : elle ferme les décisions nécessaires et crée le plan.
La première extension de `knowledge-git-api` sera limitée au besoin démontré de matérialisation d'un snapshot de lecture. Les APIs status/diff/branch/commit/merge/push restent hors scope.
Le changement de backend reste possible ultérieurement derrière le même contrat si les mesures de maintenance, portabilité ou performance justifient `gix` ou une autre solution.

View File

@@ -0,0 +1,100 @@
<!-- file: docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md -->
<!-- version: 1 -->
# Réconciliation 0.0.2 — Git générique et synchronisation
## Objet
Cette étude de `0.0.2-beta.1` réconcilie la mission annoncée avec l'implémentation réellement validée par les alphas. Elle n'ajoute aucune fonctionnalité et sert de baseline durable pour la candidate.
## Inventaire matérialisé
Le workspace contient neuf packages :
```text
knowledge-domain-lib famille/source de contenu
knowledge-git-api contrat GitRepository provider-neutral
knowledge-git-lib backend process Git contrôlé
knowledge-sync-api signaux de réconciliation
knowledge-app-lib cas d'usage partagé Changed / Unchanged
knowledge-web-lib runtime Actix/Maud/Vite partagé
knowledge-pgr-server launcher PGR
knowledge-howtos-server launcher HOWTOs
knowledge-snippets-server launcher snippets
```
Aucune crate n'est un placeholder créé uniquement pour refléter un diagramme.
## Chemin Git obtenu
Le chemin de lecture réellement matérialisé est :
```text
GitSourceConfig
-> mirror contrôlé repositories/<source-key>.git
-> remote update --prune
-> rev-parse <reference>^{commit}
-> GitRevision
-> worktree détaché snapshots/<source-key>/<revision>
-> validation HEAD + worktree propre
-> GitSnapshot
```
L'implémentation reste derrière `knowledge-git-api::GitRepository`. Les sources locales utilisent `--no-local`; les opérations sont lancées sans shell, sans prompt interactif et avec hooks neutralisés sur les opérations concernées. Les erreurs publiques n'exposent pas les localisations ou credentials de source.
## Synchronisation obtenue
`knowledge-app-lib` possède le cas d'usage partagé :
```text
RepositorySyncSignal
-> refresh
-> resolve_revision
-> comparer previous_revision
-> Unchanged
ou
-> prepare_snapshot
-> Changed { revision, snapshot }
```
La révision précédente est fournie par l'appelant ; aucune pseudo-base locale n'est ajoutée. `Periodic` utilise le même chemin de réconciliation qu'une cause explicite, sans scheduler concret.
## Autonomie normative
L'adoption PGR `v0.1.3` est fermée comme migration ponctuelle. Les règles, templates et audits nécessaires au travail courant sont possédés par Knowledge. Une session ou version normale repart de la baseline locale et ne recharge pas PGR.
## DEFER confirmés
Restent hors de `0.0.2` :
- adapter Gitea ou autre forge ;
- écriture Git, branches/propositions/rebase/merge/push et publication ;
- PostgreSQL, migrations et persistance durable de dernière révision ;
- moteur de recherche/index applicatif ;
- auth/admin complète et modèle rôles/capabilities ;
- formats finaux HOWTO/snippet et parsing métier ;
- scheduler périodique concret ;
- support explicite submodules/Git LFS ;
- politique de nettoyage/rétention des snapshots ;
- partage concurrent d'une même racine Git entre plusieurs processus ;
- backend secondaire `gix`/`git2` sans nouvelle preuve de besoin.
Ces points ne sont pas des défauts de `0.0.2`; ils nécessitent une mission distincte ou un besoin d'exploitation démontré.
## Réconciliation documentaire
Les anciennes références `alpha.2` à `alpha.5` de la fondation Web sont qualifiées `0.0.1-*` lorsque leur contexte serait ambigu avec les alphas `0.0.2`. La documentation durable distingue désormais clairement :
- fondation/runtime Web stable `0.0.1` ;
- backend Git et synchronisation `0.0.2` ;
- adoption PGR `v0.1.3` comme provenance historique, non comme dépendance courante.
## Revue d'horizon
La revue ne réserve aucune release après `0.0.2`. Plusieurs domaines futurs sont connus, mais aucun n'a encore un scope et un ordre suffisamment décidés pour devenir la prochaine mission autoritaire.
La prochaine version sera engagée uniquement lorsqu'un besoin produit ou d'exploitation choisira explicitement un axe. Aucun prompt `0.0.3` n'est créé en `beta.1`.
## Conclusion
Le scope fonctionnel de `0.0.2` est complet et cohérent avec sa mission. `beta.1` peut préparer la release candidate sans ajouter de fonctionnalité ; la RC devra se concentrer sur l'intégrité des artefacts, la reproductibilité Git, la gate Rust complète et la non-régression Web/runtime.

View File

@@ -0,0 +1,217 @@
<!-- file: docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md -->
<!-- version: 1 -->
# Étude 0.0.3 — écriture Git contrôlée
## Objet
Cette étude ouvre `0.0.3` depuis la stable autoritaire `v0.0.2` et détermine le prochain incrément utile après le chemin Git générique de lecture matérialisé en `0.0.2`.
Elle s'appuie uniquement sur les règles, l'architecture, la roadmap, les décisions différées et les preuves locales Knowledge. PGR `v0.1.3` reste une provenance historique d'adoption et aucune version PGR plus récente n'est consultée ou adoptée pour cette décision.
## Baseline observée
La stable de départ est :
```text
Knowledge tag stable v0.0.2
Knowledge commit stable 183d354a67bec6d2d512c4898f5189759a6120b0
Knowledge archive SHA-256 0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce
Knowledge publish report sasedev-knowledge-0.0.2-publish.001-report.md = PASS
```
`0.0.2` fournit déjà :
```text
source locale/remote
-> mirror contrôlé
-> refresh avec pruning
-> résolution vers commit
-> snapshot/worktree de lecture
-> synchronisation Changed / Unchanged
```
Le backend process Git contrôlé et les contrats provider-neutral sont donc acquis avant toute nouvelle capacité d'écriture.
## Revue des axes différés
La revue d'horizon `0.0.2-beta.1` laisse notamment ouverts :
- écriture Git contrôlée et publication ;
- PostgreSQL et migrations ;
- workflow complet de proposition/revue ;
- parsing final HOWTO/snippet ;
- recherche/indexation ;
- adapter Gitea ;
- auth/admin complète ;
- scheduler concret ;
- rétention de snapshots et partage multi-processus.
Le prochain axe doit apporter une capacité réellement réutilisable sans forcer plusieurs de ces décisions à la fois.
## Choix de la prochaine mission
La mission retenue est l'**écriture Git générique contrôlée avec mise à jour distante gardée**.
Ce choix est retenu parce qu'il :
- prolonge directement les responsabilités déjà matérialisées dans `knowledge-git-api` et `knowledge-git-lib` ;
- reste testable avec des repositories locaux et remotes bare temporaires, sans service externe ;
- conserve la forge optionnelle ;
- fournit la primitive nécessaire aux futurs workflows de proposition/publication sans implémenter prématurément leur état PostgreSQL, leurs rôles ou leur UI ;
- évite de sélectionner maintenant un driver/ORM PostgreSQL alors que l'écriture Git peut être fermée indépendamment ;
- transforme un `DEFER` explicitement identifié en une mission bornée sans absorber rebase/merge automatique ni publication produit complète.
`0.0.3` est donc le prochain incrément SemVer engagé.
## Stratégies d'écriture considérées
### A — worktree d'écriture isolé + commandes Git usuelles
Séquence conceptuelle :
```text
base commit explicite
-> worktree d'écriture isolé sous la racine d'état
-> mutations de fichiers validées
-> git add
-> commit avec identité explicite et hooks neutralisés
-> révision candidate
-> push gardé vers une branche autorisée
-> suppression contrôlée du worktree temporaire
```
Avantages :
- comportement proche de Git utilisé manuellement et facile à diagnostiquer ;
- réutilise le backend process déjà choisi et validé ;
- permet des tests locaux réalistes ;
- rend les mutations de fichiers et leur diff inspectables ;
- n'introduit pas de nouvelle bibliothèque Git.
Risques :
- cycle de vie d'un worktree temporaire à posséder explicitement ;
- validation stricte des chemins de mutation nécessaire ;
- nettoyage après erreur à traiter sans suppression destructive ambiguë.
### B — construction de commits par commandes Git plumbing
Une implémentation fondée sur `hash-object`, index temporaire, `write-tree` et `commit-tree` éviterait un worktree d'écriture classique.
Avantages :
- état filesystem potentiellement plus réduit ;
- contrôle fin de la construction du commit.
Inconvénients :
- complexité sensiblement supérieure pour les mutations multi-fichiers, suppressions et futurs cas de validation ;
- risque d'introduire une mini-couche Git plumbing difficile à maintenir alors que le projet possède déjà un modèle worktree pour la lecture ;
- bénéfice non démontré à ce stade.
### C — réouverture du choix `gix`/`git2`
Changer de backend uniquement pour l'écriture n'est pas retenu. `0.0.2` a déjà validé le processus `git` contrôlé comme implémentation locale remplaçable derrière le contrat. Une seconde implémentation demanderait une preuve de besoin distincte.
## Décision technique
`0.0.3` retient la stratégie A : **worktree d'écriture isolé piloté par le processus `git` existant**.
La capacité d'écriture reste séparée de la lecture dans l'API publique : `GitRepository` n'est pas transformé en interface obligatoire d'écriture pour tous ses implémenteurs. `knowledge-git-api` introduira un contrat d'écriture distinct uniquement lorsque `alpha.2` matérialisera les types nécessaires.
Aucune nouvelle crate n'est justifiée par le cadrage. L'implémentation appartient à `knowledge-git-lib` et l'orchestration métier future reste dans `knowledge-app-lib` lorsqu'un vrai cas d'usage de proposition/publication sera engagé.
## Invariants de sécurité et de cohérence
L'implémentation doit respecter les invariants suivants :
- aucune commande Git via shell ;
- prompts interactifs désactivés ;
- hooks neutralisés pour les opérations possédées par le backend ;
- identité auteur/committer fournie explicitement, sans dépendre de la configuration Git globale de la machine ;
- base d'écriture obligatoirement une `GitRevision` déjà résolue ;
- chemins de mutation obligatoirement relatifs, normalisés et confinés au worktree ;
- chemins absolus, composants `..`, accès `.git` et échappement par symlink refusés ;
- aucune mutation du snapshot de lecture stable ;
- aucune suppression/réinitialisation silencieuse d'un état inattendu ;
- le résultat d'écriture est une nouvelle révision Git explicite avant toute mise à jour distante ;
- la mise à jour distante porte uniquement sur une branche `refs/heads/...` dans `0.0.3` ;
- la branche distante est mise à jour avec une attente explicite sur son état précédent ; une divergence devient un conflit récupérable ;
- aucun `push --force` non gardé ;
- aucun rebase ou merge automatique pour masquer une divergence ;
- les erreurs publiques ne divulguent ni URL remote, ni credentials, ni arguments sensibles, ni stderr brut.
## Modèle de concurrence retenu
Le push doit utiliser une sémantique de comparaison-et-échange :
```text
expected remote revision + candidate revision + target branch
-> refresh/observation
-> push gardé
-> succès seulement si la cible possède encore l'état attendu
```
Pour une nouvelle branche, l'état attendu est l'absence de branche. Pour une branche existante, l'état attendu est une révision explicite.
Une divergence n'entraîne ni rebase ni écrasement automatique. Le futur workflow de proposition décidera comment présenter et résoudre ce conflit.
## Scope fonctionnel proposé pour 0.0.3
La release doit fournir :
- un contrat d'écriture Git distinct et provider-neutral ;
- des types de mutation de fichiers suffisamment bornés pour créer/modifier/supprimer un contenu ;
- un worktree d'écriture isolé dérivé d'une révision de base ;
- un commit avec message et identité explicites ;
- une révision candidate vérifiée ;
- un push vers une branche Git générique avec garde sur l'état distant attendu ;
- un résultat de conflit explicite lorsque la branche distante a divergé ;
- des tests d'intégration sur repositories locaux/bare temporaires couvrant succès, création de branche, modification, suppression, conflit et redaction.
## Hors scope confirmé
Restent différés :
- workflow complet de proposition/revue ;
- publication directe déclenchée par une requête utilisateur ;
- rebase/merge automatique ;
- pull/merge request de forge ;
- adapter Gitea ;
- PostgreSQL, migrations et persistance des propositions ;
- auth/admin complète ;
- signature GPG/SSH des commits ;
- Git LFS/submodules ;
- partage concurrent d'une même racine d'état entre plusieurs processus ;
- scheduler et jobs de publication ;
- formats finaux HOWTO/snippet.
## Ownership
```text
knowledge-git-api contrat passif d'écriture Git distinct du contrat de lecture
knowledge-git-lib worktree d'écriture, mutations, commit, push gardé et erreurs redacted
knowledge-app-lib inchangée tant qu'aucun cas d'usage produit de proposition/publication n'est matérialisé
```
Aucune `knowledge-forge-gitea-lib`, `knowledge-store-postgres-lib` ou autre crate candidate n'est créée par cette release uniquement pour préparer l'avenir.
## Validation attendue
Les preuves fonctionnelles doivent utiliser des repositories Git temporaires locaux et un remote bare local. Elles ne doivent dépendre ni d'Internet, ni de Gitea, ni d'un compte utilisateur Git préconfiguré.
La validation devra notamment démontrer :
- qu'un commit candidat est descendant de la base attendue ;
- qu'un fichier hors worktree ne peut pas être modifié ;
- qu'une branche absente peut être créée seulement lorsque l'appelant attend explicitement son absence ;
- qu'une branche existante peut avancer lorsque sa révision correspond à l'attente ;
- qu'une divergence concurrente produit un conflit sans écrasement ;
- que l'identité de commit ne provient pas de la configuration globale ;
- que les hooks ne s'exécutent pas ;
- que les erreurs restent expurgées.
## Conclusion
`0.0.3` est engagée comme release d'écriture Git contrôlée et push gardé. Le cadrage ne justifie aucun changement de forge, de base de données, d'authentification ou de rendu Web.

View File

@@ -0,0 +1,178 @@
<!-- file: docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md -->
<!-- version: 1 -->
# Réconciliation 0.0.3 — écriture Git et horizon produit
## Objet
Cette étude de `0.0.3-beta.1` réconcilie la mission d'écriture Git avec ce qui est réellement matérialisé par les alphas, puis réévalue l'horizon produit de SaseDev Knowledge avant la release candidate.
Elle n'ajoute aucune fonctionnalité. Elle sert de baseline durable pour stabiliser `0.0.3` et préparer la prochaine release sans consulter ni réadopter automatiquement une nouvelle version de PGR.
## Chemin Git réellement obtenu
La lecture et l'écriture restent deux capacités provider-neutral distinctes :
```text
GitRepository
refresh
resolve_revision
prepare_snapshot
GitRepositoryWriter
create_candidate
push_candidate
```
Le chemin d'écriture réellement validé est :
```text
GitRevision de base explicite
-> worktree d'écriture isolé
-> mutations create/update/delete confinées
-> commit avec identité explicite et hooks/config ambiante neutralisés
-> vérification du parent exact
-> référence interne refs/knowledge/candidates/<revision>
-> observation distante de refs/heads/...
-> push --force-with-lease contre l'état attendu
-> Updated(candidate) ou Conflict { actual }
```
Aucun adapter de forge, rebase/merge automatique ou force-push non gardé n'est nécessaire pour cette primitive.
## Invariants acquis
`0.0.3` acquiert les propriétés suivantes :
- chemins de mutation relatifs et normalisés, avec `.git`, traversées et doublons refusés ;
- vérification filesystem empêchant une traversée par symlink hors du worktree contrôlé ;
- auteur et committer fournis explicitement ;
- hooks Git désactivés pour le commit candidat ;
- configuration Git système/globale pertinente neutralisée ;
- candidat enfant direct de la base demandée ;
- candidat retenu par une ref backend interne avant nettoyage du worktree ;
- branche distante limitée à `refs/heads/...` ;
- création de branche uniquement lorsque son absence est attendue ;
- mise à jour d'une branche existante uniquement contre sa révision attendue ;
- `--force-with-lease` comme garde autoritaire contre la course concurrente ;
- divergence représentée comme conflit récupérable sans écrasement.
Ces invariants sont démontrés par des tests d'intégration utilisant uniquement des repositories et remotes bare locaux.
## DEFER reclassés
### Reclassé comme prochaine mission
La persistance PostgreSQL partagée est désormais le prochain axe structurant. Le chemin Git de lecture et d'écriture étant disponible, les futures façades ont besoin d'un état durable commun pour la configuration logique, la dernière révision synchronisée/indexée, les projections et les futurs workflows transversaux.
`0.0.4` est donc retenue comme prochaine mission :
```text
fondation PostgreSQL partagée + premier schéma + migrations
```
Cette décision n'impose pas encore :
- un driver/ORM particulier ;
- un pool particulier ;
- une stratégie Docker/service système particulière pour les tests ;
- un schéma de contenu final PGR/HOWTO/snippet ;
- auth, propositions ou moteur de recherche complet.
Ces points appartiennent à l'étude de démarrage de `0.0.4`.
### Toujours différés
Restent `DEFER` ou hors horizon numéroté tant que leur déclencheur n'est pas réel :
- rebase/merge automatique ;
- adapter Gitea ou autre forge et webhooks forge-only ;
- scheduler/jobs concrets ;
- moteur de recherche avancé au-delà des besoins de projection réellement observés ;
- quatrième application d'administration transverse ;
- partage simultané d'une même racine Git par plusieurs processus ;
- Git LFS/submodules ;
- politique avancée de rétention/cleanup ;
- observabilité ou service supplémentaire sans owner démontré.
## Horizon produit des trois sites
L'objectif produit n'est pas une succession de seules briques backend. Knowledge vise trois façades distinctes utilisant le même socle Git/PostgreSQL :
```text
pgr.sasedev.com affichage et gestion de PGR
howtos.sasedev.com affichage et gestion des HOWTOs
snippets.sasedev.com affichage et gestion des snippets
```
La revue beta distingue donc les phases suivantes.
### 0.0.4 — PostgreSQL partagé
Matérialiser la base logique commune, ses migrations et l'état minimal transversal/reconstructible sans déplacer l'autorité éditoriale hors de Git.
### 0.0.5 — modèles de contenu et projections
Travailler sur des corpus réels PGR/HOWTO/snippet, définir parsing/validation, métadonnées et relations nécessaires, puis produire les projections locales consommables par les façades.
### 0.0.6 — templates publics
Développer dans `Web/knowledge-template-lab` les références visuelles des trois sites : pages de garde, navigation, listes, détails, composants communs et états de recherche/filtrage visibles.
Cette phase valide le design et les contrats de markup/assets indépendamment du runtime Rust.
### 0.0.7 — templates d'administration
Développer les surfaces de gestion propres aux trois sites : dashboards, formulaires d'édition, listes d'objets, états de proposition/revue et retours d'erreur utiles.
Ces templates restent une référence de conception ; ils ne constituent pas encore une UI runtime complète.
### 0.0.8 — Actix Web / Maud public
Matérialiser les routes publiques réelles dans les trois launchers et adapter en Maud les templates publics validés. Les pages sont alimentées par les sources Git/projections locales et conservent le pipeline Vite/assets commun.
### 0.0.9 — Actix Web / Maud admin et autorisation
Matérialiser les besoins réels d'identité/session/capabilities, protéger les routes d'administration par façade et adapter en Maud les templates admin validés.
### 0.0.10 — gestion, proposition, revue et publication
Relier les interfaces d'administration aux cas d'usage applicatifs : édition/proposition basée sur une révision Git connue, revue, conflit explicite, création/push de la révision Git autoritaire, puis réconciliation PostgreSQL.
## Pourquoi séparer templates et Maud
Le laboratoire Web existe précisément pour concevoir et valider les références statiques sans lier chaque décision visuelle au cycle de compilation serveur.
La trajectoire retenue est donc :
```text
conception/validation template
-> contrat de composants/classes/assets utile
-> adaptation Maud
-> routes et données runtime
```
Cela évite deux écueils :
- concevoir l'interface directement dans le code Maud avant validation visuelle ;
- maintenir durablement deux produits UI indépendants, l'un statique et l'autre runtime.
La future phase d'intégration doit définir des invariants ciblés permettant de détecter une dérive significative entre les références validées et leur adaptation Maud, sans imposer une duplication exhaustive.
## Changelog et handoff
`beta.1` est la première phase où l'implémentation de `0.0.3` est suffisamment acquise pour préparer une entrée `## 0.0.3 (candidate)` décrivant les capacités réelles plutôt que les intentions alpha.
La mission `0.0.4` est également suffisamment définie pour préparer maintenant :
```text
prompts/004-V0_0_4_START_PROMPT.md
```
Le prompt ne contient pas de commit stable, SHA-256 d'archive ou résultat post-tag inventé pour `v0.0.3`. Ces preuves devront être fournies et vérifiées après publication réelle de la stable.
## Conclusion
Le scope fonctionnel de `0.0.3` est cohérent avec sa mission et peut entrer en stabilisation sans nouveau code produit. La release candidate doit geler ce résultat et rejouer la preuve complète applicable.
La trajectoire suivante est désormais lisible à l'échelle du produit : Git lecture/écriture acquis, PostgreSQL ensuite, puis contenu/projections, conception des interfaces publiques et admin, adaptation Actix/Maud et enfin workflows de gestion/publication.

View File

@@ -0,0 +1,43 @@
<!-- file: docs/templates/001-NEW_SESSION_PROMPT.md -->
<!-- version: 2 -->
# Pattern local — prompt de nouvelle session
## Usage
Ce pattern prépare le prompt versionné qui permettra d'ouvrir une future release Knowledge.
Le cycle normal crée le prompt de `N+1` pendant les dernières phases de la release `N`, après une revue d'horizon ayant suffisamment défini la mission suivante et avant la clôture stable de `N`. Il doit donc être présent dans la stable `N` publiée.
Une session normale Knowledge ne consulte pas PGR. Une mention PGR n'apparaît que lorsque la release cible contient explicitement une réévaluation/adoption PGR.
## Informations connues à la rédaction
Pendant la fin de `N`, les éléments suivants peuvent être établis :
```text
release stable attendue N
release suivante N+1
mission de N+1 décidée par la revue d'horizon
chemin du prompt prompts/NNN-<NEXT_RELEASE>_START_PROMPT.md
```
Le commit stable exact, le SHA-256 de l'archive taggée et le résultat du rapport post-tag de `N` n'existent pas encore nécessairement. Le prompt MUST NOT inventer ces valeurs.
## Forme normale du texte d'introduction de la session suivante
Après publication réelle de `N`, l'opérateur fournit :
```text
Partir de l'archive sasedev-knowledge-vX.Y.Z.zip téléchargée depuis le tag Gitea vX.Y.Z et du rapport post-tag sasedev-knowledge-X.Y.Z-publish.001-report.md. Considérer cette stable comme la baseline autoritaire de SaseDev Knowledge. Lire et exécuter prompts/NNN-<NEXT_RELEASE>_START_PROMPT.md. Vérifier intégralement l'archive, les règles et owners normatifs locaux, la gouvernance, l'architecture, la structure documentaire, les deltas/histories, les décisions DEFER, la revue d'horizon et la cohérence globale avant de préparer <NEXT_RELEASE>-alpha.1.
```
Le prompt versionné exige alors de vérifier que l'archive et le rapport fournis correspondent réellement à la stable publiée avant tout développement.
## Rattrapage exceptionnel
Si une stable a été fermée sans prompt suivant parce qu'aucune mission n'était encore décidée, le prompt MAY être créé lors de l'ouverture réelle de la release suivante. Ce cas est documenté comme rattrapage ; il ne devient pas le cycle normal.
## Réévaluation externe explicite
Si et seulement si le plan de la release contient une adoption externe, le prompt peut demander de fournir en plus l'archive/rapport exacts du référentiel concerné. Cette exception doit être visible dans le scope du prompt ; elle ne devient jamais le comportement par défaut des sessions suivantes.

37
docs/templates/002-RELEASE_PLAN.md vendored Normal file
View File

@@ -0,0 +1,37 @@
<!-- file: docs/templates/002-RELEASE_PLAN.md -->
<!-- version: 2 -->
# Pattern local — plan de release
## Contrat minimal
Un plan multi-tranches Knowledge utilise normalement :
```text
## Statut et sources
## Mission
## État acquis et questions ouvertes
## Scope
## Hors scope
## Stratégie de validation
## Trajectoire prévisionnelle
### alpha.N — ...
### beta.N — ...
### rc.N — ...
### rel.N — ...
## Déclencheurs de REPLAN
## Clôture et transition
## Revue d'horizon
```
Le nombre de tranches n'est pas fixé. Les phases annoncées restent sous `## Trajectoire prévisionnelle` avec un heading `###`.
## Règles
Le plan pilote le travail restant ; il n'est pas un journal de commandes. Les preuves détaillées vivent dans `history/` et les règles durables vivent chez leur owner local.
Un REPLAN met à jour la trajectoire et les responsabilités devenues fausses.
La revue d'horizon de fin de release doit réévaluer la prochaine mission et un horizon borné au-delà d'elle. Lorsque `N+1` est suffisamment décidée, la fin de `N` prépare aussi son prompt versionné de démarrage afin que la stable `N` contienne déjà le handoff vers la suite.
Le changelog n'est pas une intention du plan : son entrée de release est préparée vers la stabilisation à partir du résultat réellement matérialisé, puis finalisée avant la promotion stable.

29
docs/templates/003-OPERATOR_HANDOFF.md vendored Normal file
View File

@@ -0,0 +1,29 @@
<!-- file: docs/templates/003-OPERATOR_HANDOFF.md -->
<!-- version: 1 -->
# Pattern local — handoff opérateur
## Après un history de prerelease
Lorsque `origin/master` est établi :
```bash
git add -A
git diff --cached --check
git status --short
git commit -m "<VERSION_DE_LA_TRANCHE>"
git push origin master
git status --short
```
Si le delta suivant est livré dans la même réponse, son application vient seulement après ce bloc.
## Après un FAIL suivi d'un fix
Le history `FAIL` est appliqué et committé avec l'identité de la tranche en échec avant d'appliquer `.fix.N`. Il n'est jamais remplacé ou fusionné avec le futur history `PASS`.
## Première fermeture stable
La première tranche `rel.N[/fix.M]` qui obtient le `PASS` stable est committée avec le message produit `X.Y.Z`, puis le tag `vX.Y.Z` est créé/poussé selon la politique locale et vérifié avant le rapport post-tag.
Les commandes exactes de tag/publication sont fournies avec les valeurs réellement établies au moment de la clôture.

View File

@@ -0,0 +1,37 @@
<!-- file: docs/templates/004-RUST_LIBRARY_CRATE.md -->
<!-- version: 1 -->
# Pattern local — crate bibliothèque Rust
## But
Ce pattern illustre la structure minimale attendue d'une crate bibliothèque Knowledge. Il ne crée pas une crate par anticipation et ne remplace pas [`../rules/510-RUST_CARGO.md`](../rules/510-RUST_CARGO.md).
## Façade
`src/lib.rs` reste une façade :
```rust
//! Documentation de crate.
mod responsibility;
/// Contrat public exposé par la crate.
pub use self::responsibility::PublicItem;
```
Les définitions résident dans le module propriétaire :
```rust
// file: crates/<crate>/src/responsibility.rs
// version: 1
/// Contrat public possédé par cette responsabilité.
pub struct PublicItem;
```
Pour plusieurs responsabilités distinctes, créer plusieurs modules nommés selon leur rôle réel. Ne pas créer un fichier `api.rs`, `common.rs` ou `utils.rs` uniquement pour déplacer du code sans clarifier l'ownership.
## Tests
Les tests unitaires vivent sous `unit_tests/` et sont rattachés au module source correspondant. Les tests d'intégration vivent sous `tests/` et consomment l'API publique.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
<!-- version: 17 -->
<!-- version: 41 -->
# Matrice de validation Knowledge
@@ -117,6 +117,156 @@ Les tests doivent permettre plusieurs `source_id` et ne pas confondre famille de
Un test live de remote ou de forge doit être explicitement classé comme externe, séparé des tests unitaires et ne doit pas devenir une condition cachée pour les opérations locales.
## Gate de `0.0.3-alpha.2`
La tranche de contrat d'écriture Git doit prouver :
- `VERSION = 0.0.3-alpha.2` et version workspace identique ;
- `knowledge-git-api` reste une crate passive sans dépendance de forge, runtime, filesystem backend ou PostgreSQL ;
- `GitRepository` de lecture reste inchangé et implémentable sans capacité d'écriture ;
- `GitRepositoryWriter` est une capacité distincte ;
- les chemins de mutation refusent valeur vide, chemin absolu/séparateur plateforme, `.` / `..`, composant vide et `.git` ;
- les mutations représentent écriture create/update et suppression, avec rejet des chemins dupliqués dans une même requête ;
- l'identité de commit et le message sont explicites et validés ;
- la branche cible est une référence complète valide sous `refs/heads/...` ;
- l'état distant attendu représente explicitement branche absente ou révision connue ;
- le résultat de push peut représenter succès ou conflit sans rebase/merge automatique ;
- `cargo fmt --all`, check, Clippy strict et tests ciblés `knowledge-git-api` passent ;
- les tests workspace passent car la façade publique de la crate change ;
- `knowledge-git-lib`, `knowledge-app-lib`, `Web/`, PostgreSQL et adapters forge ne sont pas modifiés par cette tranche.
## Gate de `0.0.3-alpha.3`
La tranche de création locale du commit candidat doit prouver :
- `VERSION = 0.0.3-alpha.3` et version workspace identique ;
- history `history/0.0.3/alpha.2.md` présent avec statut `PASS` et committé avant application ;
- `knowledge-git-api` inchangé par rapport à `alpha.2` ;
- `knowledge-git-lib` crée un worktree d'écriture isolé sous sa racine d'état sans modifier les snapshots de lecture ;
- create/update/delete limités exactement aux chemins de mutation demandés ;
- toute traversée par symlink refusée avant écriture hors worktree ;
- configuration Git système/globale pertinente neutralisée et hooks désactivés ;
- identité auteur/committer explicitement fournie par `GitWriteRequest` ;
- commit candidat vérifié comme enfant direct de la révision de base ;
- candidat retenu sous une référence backend interne avant suppression du worktree temporaire ;
- worktree temporaire supprimé après succès et après erreur possédée, mais worktree préexistant jamais effacé silencieusement ;
- aucun push distant, rebase, merge, forge, PostgreSQL ou orchestration application ajouté ;
- test d'intégration Git réel prouvant modification/création/suppression, identité explicite, base inchangée et nettoyage ;
- test Unix prouvant qu'un symlink versionné ne permet pas d'écrire hors du worktree ;
- `cargo fmt --all`, check, Clippy strict, tests ciblés `knowledge-git-lib` et tests workspace propres ;
- `knowledge-app-lib` et `Web/` sans diff, `Cargo.lock` ignoré/non suivi et staging final propre.
## Gate de `0.0.3-alpha.4`
La tranche de push gardé et concurrence doit prouver :
- `VERSION = 0.0.3-alpha.4` et version workspace identique ;
- history `history/0.0.3/alpha.3.fix.1.md` présent avec statut `PASS` et committé avant application ;
- `GitBackend` implémente désormais `GitRepositoryWriter` sans modifier le contrat public `knowledge-git-api` ;
- le candidat à pousser doit exister sous la référence interne contrôlée créée par `alpha.3` ;
- l'état distant est observé par le remote générique `origin`, sans API de forge ;
- une attente `GitRemoteState::Revision(r)` utilise une lease exacte sur `r` ;
- une attente `GitRemoteState::Absent` n'autorise que la création d'une branche absente ;
- un écart observé avant le push produit immédiatement `GitPushOutcome::Conflict` ;
- `--force-with-lease` protège également la course entre observation et push ;
- après rejet d'un push, une nouvelle observation traduit une divergence en conflit récupérable sans écrasement ;
- aucun rebase/merge automatique, force-push non gardé, forge, PostgreSQL ou orchestration applicative n'est ajouté ;
- tests d'intégration Git réels avec remote bare local couvrant mise à jour d'une branche existante, création sous attente d'absence et conflit concurrent sans écrasement ;
- `cargo fmt --all`, check, Clippy strict, tests ciblés `knowledge-git-lib` et tests workspace propres ;
- `knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` et `ROADMAP.md` sans diff ;
- `Cargo.lock` ignoré/non suivi et staging final propre.
## Gate de `0.0.3-beta.1`
La tranche de réconciliation/stabilisation doit prouver :
- `VERSION = 0.0.3-beta.1` et version workspace identique ;
- history `history/0.0.3/alpha.4.md` présent avec statut `PASS` et committé avant application ;
- aucun diff sous `crates/` ou `Web/` : la beta ne change pas le produit ;
- `CHANGELOG.md` contient `## 0.0.3 (candidate)` construit à partir des capacités réellement acquises ;
- étude `007-V0_0_3_GIT_WRITE_RECONCILIATION.md` présente et cohérente avec l'architecture ;
- `ROADMAP.md` retient `0.0.4` comme prochaine mission potentielle et explicite les phases templates publics/admin puis Actix/Maud pour les trois sites ;
- `prompts/004-V0_0_4_START_PROMPT.md` présent sans commit stable, SHA d'archive ou rapport post-tag `v0.0.3` inventé ;
- `0.0.4` n'introduit encore aucun driver/ORM, crate PostgreSQL, migration ou dépendance pendant la gate `0.0.3` ;
- rebase/merge automatique, forge, scheduler, recherche avancée, quatrième admin et partage multi-processus restent différés ;
- `cargo fmt --all`, audits Rust, metadata/check, Clippy strict, tests Git ciblés et tests workspace passent ;
- baseline Web `npm ci`, typecheck et build Vite passent ;
- manifest Vite de production reste parsable avec l'entrée stable `src/main.ts` ;
- les trois launchers restent compilables et la RC devra au minimum rejouer les smokes HTTP complets ;
- `Cargo.lock` local reste ignoré/non suivi, package-lock Web versionné et staging final propre.
Un `PASS` autorise uniquement `0.0.3-rc.1`. Toute régression produit exige un fix beta borné ou un retour de phase ; la beta ne doit pas masquer un défaut sous une réécriture documentaire.
## Gate de `0.0.3-rc.1`
La release candidate doit prouver :
- `VERSION = 0.0.3-rc.1` et version workspace identique ;
- history `history/0.0.3/beta.1.md` présent avec statut `PASS` et committé avant application ;
- scope produit gelé : aucun diff sous `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/` ou `prompts/` ;
- tous les histories antérieurs de `0.0.3` présents avec leurs statuts réels et `rc.1.md` seul delta sans history correspondant ; les contrôles de statut doivent comparer la valeur sémantique après retrait d'un éventuel balisage inline Markdown historique, sans réécrire les histories ;
- `CHANGELOG.md` toujours en `## 0.0.3 (candidate)` et `ROADMAP.md` conservant `0.0.3` engagée avec l'horizon validé en beta ;
- handoff `prompts/004-V0_0_4_START_PROMPT.md` présent et toujours sans commit/SHA futur inventé ;
- `cargo fmt --all`, audits Rust, metadata/check, Clippy strict, tests Git ciblés et tests workspace passent ;
- baseline Web `npm ci`, typecheck, build Vite et manifest de production passent ;
- les trois launchers passent un smoke HTTP de production sur `/healthz`, `/` et le service effectif des assets référencés par le manifest ;
- PostgreSQL, migrations, forge, rebase/merge automatique, auth/admin et workflow produit différés restent absents ;
- `Cargo.lock` local reste ignoré/non suivi, package-lock Web versionné et staging final propre.
Un `PASS` autorise uniquement `0.0.3-rel.1`. Toute régression fonctionnelle exige un fix RC borné ou un retour de phase approprié ; aucune fonctionnalité ne peut être ajoutée en RC.
## Gate de `0.0.3-rc.1.fix.1`
Le fix de validation RC doit prouver :
- `VERSION = 0.0.3-rc.1.fix.1` et version workspace identique ;
- history `history/0.0.3/rc.1.md` présent avec statut `FAIL` et committé avant application ;
- `alpha.1` toujours historiquement `PARTIAL` sans modification de son fichier ;
- lecture robuste des statuts historiques, qu'ils soient bruts ou entourés de backticks Markdown ;
- aucun diff produit sous `crates/` ou `Web/` et aucun changement du changelog, de la roadmap, de l'architecture, des études, des règles ou des prompts ;
- audits Markdown/Rust locaux, `cargo fmt`, metadata/check, Clippy strict, tests Git ciblés et tests workspace propres ;
- présentation candidate `0.0.3` et handoff `0.0.4` inchangés ;
- `Cargo.lock` local ignoré/non suivi, package-lock Web versionné et staging final propre.
Un `PASS` autorise `0.0.3-rel.1`. Le fix ne rejoue pas les smokes HTTP ni le build Web déjà `PASS` sur `rc.1`, puisqu'aucun fichier produit ou Web n'est modifié ; la gate stable `rel.1` les rejouera avant publication.
## Gate de `0.0.3-rel.1`
La fermeture stable pré-tag doit prouver :
- `VERSION = 0.0.3` et version workspace identique ;
- history `history/0.0.3/rc.1.fix.1.md` présent avec statut `PASS` et committé avant application ;
- `rel.1` comme seul delta `0.0.3` sans history correspondant pendant sa gate ;
- changelog stable `## 0.0.3` sans marqueur `candidate` ;
- roadmap avec `0.0.3` terminée, `0.0.4` engagée et `0.0.5` à `0.0.10` conservées comme horizons révisables ;
- README, index documentaire et plan `0.0.3` présentés comme stables/historiques ;
- prompt `004` présent, sans commit/SHA post-tag inventé et sans consultation PGR automatique ;
- `cargo fmt --all`, audits Rust, metadata/check, Clippy strict, tests Git ciblés et tests workspace passent ;
- baseline Web `npm ci`, typecheck, build Vite et manifest passent ;
- les trois smokes HTTP production passent sur `/healthz`, `/` et les assets du manifest ;
- PostgreSQL, migrations, forge, auth/admin et workflows encore différés restent absents du code produit de `0.0.3` ;
- `Cargo.lock` local reste ignoré/non suivi, package-lock Web versionné et staging final propre ;
- aucun tag stable ni rapport post-tag n'est déclaré comme existant avant le history `rel.1` `PASS` et le commit stable.
Un `PASS` de `rel.1` autorise le commit produit exact `0.0.3`, le push, puis seulement la création/push du tag annoté `v0.0.3`, le téléchargement de l'archive réellement servie et la production séparée du rapport `sasedev-knowledge-0.0.3-publish.001-report.md`.
## Gate de `0.0.3-rel.1.fix.1`
Le fix de complétion de preuve stable doit prouver :
- `VERSION = 0.0.3` et version workspace identique ; la clôture stable reste la même version produit ;
- history `history/0.0.3/rel.1.md` présent avec statut `PARTIAL` et committé avant application ;
- aucune modification sous `crates/`, `Web/`, `CHANGELOG.md`, `ROADMAP.md`, `docs/architecture/`, `docs/studies/`, `docs/rules/` ou `prompts/` ;
- fichier `cargo metadata --format-version 1 --no-deps` produit puis ensemble exact des neuf packages/workspace members vérifié explicitement ;
- hygiène texte explicite `PASS` sur README/RULES/ROADMAP/CHANGELOG/docs/prompts/deltas/history ;
- audits Markdown locaux propres après ajout du fix ;
- preuves `rel.1` conservées : formatage/audits Rust, Cargo/Clippy/tests, Web/Vite et trois smokes HTTP tous `PASS` ;
- tag `v0.0.3` et rapport post-tag toujours absents avant le history `rel.1.fix.1` `PASS` ;
- `Cargo.lock` local ignoré/non suivi, package-lock Web versionné et staging final propre.
Le premier `PASS` d'une clôture `rel.N[/fix.M]` autorise alors le commit produit exact `0.0.3`, le push, puis le tag annoté `v0.0.3` et la vérification post-tag.
## PostgreSQL
Aucune gate PostgreSQL n'est créée avant matérialisation d'une dépendance ou d'un schéma réel.
@@ -487,3 +637,383 @@ La candidate finale doit prouver :
- staging final sans `Cargo.lock` et `git diff --cached --check` propre.
Un `PASS` de `rc.2` autorise uniquement `0.0.1-rel.1`.
## Gate de `0.0.2-alpha.1`
La première tranche de `0.0.2` est documentaire et de gouvernance. Elle doit prouver :
- `VERSION = 0.0.2-alpha.1` et `[workspace.package].version` identique ;
- baseline Knowledge `v0.0.1` identifiée par son commit, SHA-256 d'archive et rapport `publish.001 PASS` ;
- adoption PGR `v0.1.3` explicite avec commit, SHA-256 d'archive et rapport `publish.001 PASS` ;
- owner `230-ADOPTION` actif uniquement pour la migration du projet existant ;
- preservation des références historiques PGR `v0.1.1` dans les artefacts `0.0.1` ;
- absence d'activation Java/Maven/Gradle/Android et maintien du `DEFER` Tauri ;
- étude comparative Git présente et décision `processus git contrôlé` explicite ;
- `knowledge-git-lib` identifiée comme owner futur de la configuration physique/backend sans création prématurée de la crate ;
- racine d'état Git explicitement hors dépôt source et layout mirror/snapshot documenté ;
- `knowledge-app-lib` seulement comme owner candidat du cas d'usage partagé, sans placeholder ;
- aucun diff sous `crates/` ou `Web/` ;
- aucun history `0.0.2/alpha.1` présent avant la gate ;
- headers, liens Markdown locaux et hygiène texte propres ;
- audits Rust existants propres ;
- Cargo/Clippy/tests workspace propres malgré le bump de version ;
- baseline Web `npm ci`, typecheck et build sans régression ;
- `Cargo.lock` toujours ignoré/non suivi et `package-lock.json` toujours versionné ;
- `git diff --cached --check` propre avant commit.
La disponibilité de l'exécutable `git` et sa version sont relevées comme prérequis du backend futur :
```text
git --version
```
`alpha.1` ne prétend pas encore valider `knowledge-git-lib` puisqu'aucune implémentation fonctionnelle n'existe dans cette tranche. La séquence mirror/refresh/resolve/worktree documentée dans l'étude est une prévalidation de conception, pas une gate produit.
## Gate de `0.0.2-alpha.1.fix.1`
Le fix reste documentaire et ne change aucune décision technique Git. Il doit prouver :
- `VERSION = 0.0.2-alpha.1.fix.1` et `[workspace.package].version` identique ;
- history `history/0.0.2/alpha.1.md` présent avec statut réel `FAIL` et committé avant application du fix ;
- delta historique `deltas/0.0.2/alpha.1.md` inchangé ;
- `docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md` en version documentaire `2` ;
- présence, dans cet ordre, des sections `Statut et sources`, `Mission`, `État acquis et questions ouvertes`, `Scope`, `Hors scope`, `Stratégie de validation`, `Trajectoire prévisionnelle`, `Déclencheurs de REPLAN`, `Clôture et transition`, `Revue d'horizon` ;
- toutes les tranches prévues `alpha.1`, `alpha.2`, `alpha.3`, `alpha.4`, `beta.1`, `rc.1` et `rel.1` déclarées en titres `###` sous `## Trajectoire prévisionnelle` ;
- aucun titre `## alpha.*`, `## beta.*`, `## rc.*` ou `## rel.*` dans le plan ;
- le `FAIL` documentaire d'`alpha.1` et le rôle correctif d'`alpha.1.fix.1` explicitement documentés ;
- scope global et hors-scope de `0.0.2` distincts du forecast tranche par tranche ;
- déclencheurs de REPLAN couvrant au minimum dérive de responsabilités, forge obligatoire, capacités Git supplémentaires, concurrence de l'état, changement substantiel des contrats et accumulation de fixes ;
- revue d'horizon explicitement non réalisée avant `beta.1`, sans version postérieure à `0.0.2` réservée ;
- aucun changement sous `crates/` ou `Web/` ;
- aucune dépendance, crate, route ou comportement Git ajouté par le fix ;
- headers/liens Markdown, hygiène texte et audits Rust existants propres ;
- Cargo/Clippy/tests workspace et baseline Web toujours propres malgré le bump de version ;
- `Cargo.lock` reste ignoré/non suivi et `package-lock.json` reste versionné ;
- `git diff --cached --check` propre avant commit.
Le `PASS` du fix ferme `alpha.1` et autorise uniquement la préparation de `0.0.2-alpha.2`.
## Gate de `0.0.2-alpha.2`
La première tranche fonctionnelle Git doit prouver :
- `VERSION = 0.0.2-alpha.2` et `[workspace.package].version` identique ;
- history `alpha.1.fix.1` `PASS` committé avant application ;
- huit packages Cargo exactement, avec `knowledge-git-lib` comme nouvelle crate `-lib` réelle ;
- `knowledge-git-api` et `knowledge-sync-api` toujours passives et inchangées ;
- `knowledge-git-lib` dépendant uniquement des contrats/domaines réellement nécessaires, sans Actix, forge, PostgreSQL, scheduler ou runtime async ;
- racine d'état obligatoire et explicite, avec sous-répertoires `hooks-disabled/`, `repositories/` et `snapshots/` ;
- dérivation de `source-key` ne réutilisant ni `SourceId` brut ni URL remote comme chemin ;
- source locale utilisant `git clone --mirror --no-local -- ...` ;
- source remote utilisant `git clone --mirror -- ...` sans `--no-local` ;
- runner process interne utilisant `std::process::Command` directement, sans shell ;
- prompts Git interactifs désactivés et variables Git capables de rediriger implicitement repository/worktree retirées ;
- hooks Git désactivés via un répertoire contrôlé ;
- erreurs de processus publiques ne contenant ni URL source, ni arguments, ni stderr, ni credentials ;
- inspection d'un mirror existant sans suppression/recréation silencieuse ;
- tests unitaires ciblant configuration, layout, arguments, redaction et erreurs ;
- aucun refresh, résolution de révision ou snapshot fonctionnel anticipé avant `alpha.3` ;
- aucun adapter Gitea/GitHub/GitLab, PostgreSQL, scheduler ou `knowledge-app-lib` ajouté ;
- `cargo fmt --all -- --check`, audits Rust, `cargo metadata`, `cargo check`, Clippy strict et tests ciblés/workspace propres ;
- baseline Web inchangée et absence de diff sous `Web/` ;
- `Cargo.lock` toujours ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` d'`alpha.2` autorise uniquement la préparation de `0.0.2-alpha.3`, consacrée au refresh réel, à la résolution et au snapshot sur repositories Git temporaires.
## Gate de `0.0.2-alpha.2.fix.1`
Le fix reste strictement dans le scope `alpha.2` et doit prouver :
- `VERSION = 0.0.2-alpha.2.fix.1` et `[workspace.package].version` identique ;
- history `history/0.0.2/alpha.2.md` présent avec statut réel `FAIL` et committé avant application du fix ;
- delta historique `deltas/0.0.2/alpha.2.md` inchangé ;
- suppression du défaut `git diff --check` sur la matrice de validation ;
- audit Markdown limité au contenu gouverné et ignorant notamment `.git`, `node_modules`, `target` et autres arbres générés ;
- correction de la construction interne de `GitSourceKey` sans rendre son champ tuple public ;
- formatage rustfmt propre des fichiers de `knowledge-git-lib` ;
- huit packages Cargo exactement et dépendances de `knowledge-git-lib` inchangées ;
- absence de `refresh`, résolution de révision, worktree/snapshot, forge, PostgreSQL, scheduler ou `knowledge-app-lib` ;
- audits Rust, `cargo metadata`, `cargo check`, Clippy strict, tests ciblés `knowledge-git-lib` et tests workspace propres ;
- `Web/`, `knowledge-git-api` et `knowledge-sync-api` sans diff ;
- `Cargo.lock` toujours ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` du fix ferme `alpha.2` et autorise uniquement la préparation de `0.0.2-alpha.3`.
## Gate de `0.0.2-alpha.2.fix.2`
Le second fix reste strictement dans le scope `alpha.2` et doit prouver :
- `VERSION = 0.0.2-alpha.2.fix.2` et `[workspace.package].version` identique ;
- history `history/0.0.2/alpha.2.fix.1.md` présent avec statut réel `FAIL` et committé avant application du fix ;
- deltas historiques `alpha.2.md` et `alpha.2.fix.1.md` inchangés ;
- correction limitée à la violation `clippy::implicit_return` observée dans `unit_tests/backend.rs` ;
- retour explicite dans la closure de validation du `source-key` ;
- aucune modification fonctionnelle du backend Git, de ses erreurs publiques ou de son API ;
- huit packages Cargo exactement et dépendances de `knowledge-git-lib` inchangées ;
- absence de `refresh`, résolution de révision, worktree/snapshot, forge, PostgreSQL, scheduler ou `knowledge-app-lib` ;
- `git diff --check`, audits structurels, `cargo fmt`, `cargo metadata`, `cargo check`, Clippy strict, tests ciblés `knowledge-git-lib` et tests workspace propres ;
- `Web/`, `knowledge-git-api` et `knowledge-sync-api` sans diff ;
- `Cargo.lock` toujours ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` de `alpha.2.fix.2` ferme `alpha.2` et autorise uniquement la préparation de `0.0.2-alpha.3`.
## Gate de `0.0.2-alpha.3`
La tranche de lecture Git réelle doit prouver :
- `VERSION = 0.0.2-alpha.3` et `[workspace.package].version` identique ;
- history `history/0.0.2/alpha.2.fix.2.md` présent avec statut `PASS` et committé avant application ;
- huit packages Cargo exactement ;
- `knowledge-git-lib` dépend désormais de `knowledge-domain-lib` et `knowledge-git-api`, sans autre dépendance ;
- `knowledge-git-api` étendu uniquement avec le contrat `GitSnapshot` / `prepare_snapshot` nécessaire à la lecture ;
- `GitBackend` implémente `knowledge_git_api::GitRepository` et possède l'enregistrement explicite des configurations physiques de source ;
- doublons de `SourceId` refusés et source non configurée signalée sans exposer sa localisation ;
- refresh du mirror par `git --git-dir <mirror> remote update --prune` ;
- résolution par `rev-parse --verify --end-of-options <reference>^{commit}` ;
- révision backend validée comme identité hexadécimale non vide avant usage dans un chemin, sans longueur SHA figée ;
- snapshot créé par `worktree add --detach -- <path> <revision>` sous `snapshots/<source-key>/<revision>` ;
- snapshot existant réutilisé seulement si son `HEAD` correspond exactement à la révision et si `status --porcelain=v1 --untracked-files=all` est vide ;
- snapshot divergent ou sale refusé sans suppression silencieuse ;
- tests avec vrais repositories Git temporaires prouvant initialisation/refresh/résolution/snapshot, réutilisation idempotente, changement de commit, référence invalide et snapshot sale ;
- aucune API de forge, dépendance réseau Rust, PostgreSQL, scheduler, `knowledge-app-lib` ou écriture/push Git ajoutée ;
- `cargo fmt --all -- --check`, audits Rust, `cargo metadata`, `cargo check`, Clippy strict, tests ciblés et tests workspace propres ;
- `Web/` et `knowledge-sync-api` sans diff ;
- `Cargo.lock` toujours ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
La gate opérateur réelle d'`alpha.3` est `FAIL` : les preuves Git fonctionnelles passent, mais `git diff --check`, `cargo fmt --check` et Clippy strict ne sont pas tous propres. La transition autorisée est uniquement `alpha.3.fix.1`.
## Gate de `0.0.2-alpha.3.fix.1`
Le fix ferme les défauts techniques d'`alpha.3` et enregistre le REPLAN sans élargir son comportement Git. Il doit prouver :
- `VERSION = 0.0.2-alpha.3.fix.1` et `[workspace.package].version` identique ;
- history `history/0.0.2/alpha.3.md` présent avec statut réel `FAIL` et committé avant application du fix ;
- delta historique `deltas/0.0.2/alpha.3.md` inchangé ;
- suppression du défaut `git diff --check` sur la matrice de validation ;
- formatage rustfmt propre du test `knowledge-git-api` signalé par la gate ;
- documentation de crate explicite dans `crates/knowledge-git-lib/tests/git_read_path.rs` afin de satisfaire `missing_docs` ;
- comportement Git d'`alpha.3` inchangé : refresh, résolution, snapshot et tests réels restent identiques ;
- plan `0.0.2` replanifié avec `alpha.4` dédiée à l'autonomie normative/structure interne des crates et ancien scope de synchronisation déplacé en `alpha.5` ;
- PGR `v0.1.3` documenté comme provenance d'adoption figée et non comme dépendance normative vivante ;
- règle locale interdisant la consultation automatique de PGR à chaque version/session/tranche, sauf réévaluation PGR explicitement engagée ;
- aucune matérialisation prématurée des règles PGR détaillées ni refactor massif de crates dans ce fix : ce travail appartient à `alpha.4` ;
- `cargo fmt --all -- --check`, audits Rust, `cargo metadata`, `cargo check`, Clippy strict, tests ciblés `knowledge-git-api`/`knowledge-git-lib`, test d'intégration `git_read_path` et tests workspace propres ;
- `Web/` et `knowledge-sync-api` sans diff ;
- `Cargo.lock` toujours ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` de `alpha.3.fix.1` ferme `alpha.3` et autorise uniquement la préparation de `0.0.2-alpha.4`.
## Gate de `0.0.2-alpha.4`
La tranche d'autonomie normative et de structure interne doit prouver :
- `VERSION = 0.0.2-alpha.4` et version workspace identique ;
- history `history/0.0.2/alpha.3.fix.1.md` présent avec statut `PASS` et committé avant application ;
- owners locaux `200`, `210`, `300`, `400`, `510` et `530-WEB_FRONTEND` présents, indexés et suffisamment complets pour retrouver localement lifecycle/versioning, fixes/REPLAN, delta/history/handoff, rôles documentaires et catégories de preuve ;
- mapping d'adoption `003-PGR_ADOPTION_MAP.md` présent, PGR `v0.1.3` seulement comme provenance et aucune consultation PGR automatique requise ;
- templates locaux de nouvelle session, release plan, handoff opérateur et crate bibliothèque présents ;
- audits Markdown headers/liens/tables possédés localement et propres ;
- audit `audit_rust_crate_root_facades.py` propre sur toutes les crates bibliothèque ;
- `knowledge-git-api/src/lib.rs` limité à documentation/module declarations/réexports/test attachment, avec définitions déplacées vers `reference`, `repository`, `revision`, `snapshot` ;
- `knowledge-sync-api/src/lib.rs` limité à façade, avec définitions déplacées vers `event_source` et `signal` ;
- API publique crate-root inchangée pour `GitReference`, `GitRevision`, `GitSnapshot`, `GitRepository`, `RepositorySyncCause`, `RepositorySyncSignal`, `RepositoryEventSource` ;
- `knowledge-domain-lib`, `knowledge-git-lib` et `knowledge-web-lib` déjà conformes à la façade crate-root sans refactor artificiel ;
- comportement Git `alpha.3` inchangé et tests réels toujours propres ;
- `cargo fmt --all -- --check`, audits Rust, `cargo metadata`, `cargo check`, Clippy strict, tests ciblés `knowledge-git-api`/`knowledge-sync-api`/`knowledge-git-lib` et tests workspace propres ;
- `Web/` sans diff et aucun `knowledge-app-lib`, adapter forge ou store PostgreSQL ajouté ;
- `Cargo.lock` ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` d'`alpha.4` ferme l'adoption PGR `v0.1.3` comme migration ponctuelle et autorise uniquement la préparation de `0.0.2-alpha.5`.
## Gate de `0.0.2-alpha.4.fix.1`
Le correctif doit prouver :
- `VERSION = 0.0.2-alpha.4.fix.1` et version workspace identique ;
- history `history/0.0.2/alpha.4.md` présent avec statut `FAIL` et committé avant application ;
- `GitReference::new` construit le tuple struct privé avec `Self(...)` depuis `reference.rs` ;
- `GitRevision::new` construit le tuple struct privé avec `Self(...)` depuis `revision.rs` ;
- aucune ouverture de visibilité des champs tuple et aucune construction via `crate::GitReference(...)`, `crate::GitRevision(...)` ou chemin de module propriétaire ;
- API publique crate-root inchangée et audit `audit_rust_crate_root_facades.py` toujours propre ;
- owners locaux, mapping d'adoption, templates et audits d'autonomie d'`alpha.4` inchangés et toujours valides ;
- `cargo fmt --all -- --check`, `cargo check --workspace` et Clippy strict `PASS` ;
- tests ciblés `knowledge-git-api`, `knowledge-sync-api`, `knowledge-git-lib` et tests workspace `PASS` ;
- comportement Git `alpha.3` inchangé ;
- `Web/`, `knowledge-git-lib/src` et les modules `knowledge-sync-api` sans diff ;
- `Cargo.lock` ignoré/non suivi et `git diff --cached --check` propre.
Le `PASS` de `alpha.4.fix.1` ferme `alpha.4`, termine l'adoption PGR `v0.1.3` comme migration ponctuelle et autorise uniquement la préparation de `0.0.2-alpha.5`.
## Ordre canonique des validations Rust à partir de `0.0.2-alpha.5`
Toute gate qui audite du code Rust applique d'abord le formatter :
```bash
cargo fmt --all
cargo fmt --all -- --check
```
Les audits Rust sont exécutés seulement après ces deux commandes. Viennent ensuite `cargo metadata`/`cargo check`, Clippy et les tests requis par le scope. Les audits Markdown peuvent être placés avant ou après cette séquence puisqu'ils ne lisent pas la forme du code Rust.
## Gate de `0.0.2-alpha.5`
La tranche de cas d'usage de synchronisation doit prouver :
- `VERSION = 0.0.2-alpha.5` et version workspace identique ;
- history `history/0.0.2/alpha.4.fix.1.md` présent avec statut `PASS` et committé avant application ;
- neuf packages Cargo exactement, avec `knowledge-app-lib` comme nouvel owner application réel ;
- `knowledge-app-lib` dépendant seulement de `knowledge-git-api` et `knowledge-sync-api` en production ;
- `src/lib.rs` de `knowledge-app-lib` conforme au contrat de façade crate-root ;
- cas d'usage public enchaînant refresh, résolution, comparaison de `previous_revision` puis snapshot seulement si changement ;
- résultat explicite `Changed` / `Unchanged` exposant toujours la révision observée et le snapshot uniquement en cas de changement ;
- absence de persistance implicite de la dernière révision ;
- cause `Periodic` prouvée sur le même chemin que la synchronisation explicite, sans scheduler concret ;
- aucune dépendance forge, PostgreSQL, Actix, worker réseau ou écriture Git dans `knowledge-app-lib` ;
- `cargo fmt --all` exécuté avant tout audit Rust ;
- `cargo fmt --all -- --check` exécuté immédiatement après le formatage et avant tout audit Rust ;
- audits Rust locaux propres, y compris façade crate-root et complétude d'exports ;
- `cargo metadata`, `cargo check --workspace`, Clippy strict, tests ciblés `knowledge-app-lib`/Git et tests workspace propres ;
- `Web/` sans diff et `Cargo.lock` ignoré/non suivi ;
- `git diff --cached --check` propre avant commit.
Le `PASS` d'`alpha.5` ferme le scope fonctionnel alpha de `0.0.2` et autorise uniquement la préparation de `beta.1` de réconciliation et revue d'horizon.
## Gate de `0.0.2-beta.1`
La réconciliation beta doit prouver :
- `VERSION = 0.0.2-beta.1` et version workspace identique ;
- history `history/0.0.2/alpha.5.md` présent avec statut `PASS` et committé avant application ;
- aucun nouveau package Cargo, package npm, contrat runtime ou comportement Git introduit ;
- workspace Cargo composé exactement de neuf packages ;
- étude `005-V0_0_2_GIT_SYNC_RECONCILIATION.md` présente et cohérente avec l'implémentation ;
- `knowledge-git-lib` documentée comme backend Git générique réel et `knowledge-app-lib` comme owner du cas d'usage partagé ;
- références historiques aux alphas Web de fondation qualifiées `0.0.1-*` lorsqu'elles seraient ambiguës ;
- `DEFER` PostgreSQL/forge/auth/admin/formats finaux/écriture Git/scheduler/submodules-LFS/rétention snapshots/multi-processus conservés ;
- revue d'horizon réalisée sans engager ni numéroter de release après `0.0.2` ;
- `CHANGELOG.md` contient une section `0.0.2 (candidate)` utile et sans catalogue de fixes internes ;
- `cargo fmt --all` puis `cargo fmt --all -- --check` exécutés avant tout audit Rust ;
- audits Markdown et Rust locaux propres ;
- `cargo metadata`, `cargo check --workspace`, Clippy strict et tests workspace propres ;
- baseline Web `npm ci`, `npm run typecheck` et `npm run build` propre ;
- manifest Vite généré parsable avec l'entrée stable `src/main.ts` ;
- aucun diff sous `crates/` ou `Web/` ;
- `Cargo.lock` présent localement, ignoré et non suivi ;
- hygiène texte et `git diff --cached --check` propres.
Un `PASS` de `beta.1` autorise uniquement `0.0.2-rc.1`. Aucun prompt de release suivante n'est créé tant que la revue d'horizon ne l'a pas engagée.
## Gate de `0.0.2-beta.1.fix.1`
Le fix de clôture beta doit prouver :
- `VERSION = 0.0.2-beta.1.fix.1` et version workspace identique ;
- history `history/0.0.2/beta.1.md` présent avec statut `PARTIAL` et committé avant application ;
- aucun nouveau package Cargo, package npm, contrat runtime ou comportement Git ;
- aucun diff sous `crates/` ou `Web/` avant le formatage ;
- revue d'horizon et étude `005` inchangées dans leur conclusion : aucune release après `0.0.2` engagée ;
- `cargo fmt --all` puis `cargo fmt --all -- --check` exécutés avant tout audit Rust ;
- audits Markdown et Rust locaux propres ;
- `cargo metadata`, `cargo check --workspace`, Clippy strict et tests workspace propres ;
- baseline Web `npm ci`, `npm run typecheck` et `npm run build` propre ;
- manifest Vite généré parsable avec l'entrée stable `src/main.ts` ;
- smoke HTTP de `knowledge-pgr-server`, `knowledge-howtos-server` et `knowledge-snippets-server` `PASS`, vérifiant `/healthz`, `/`, les références du manifest et le service effectif des assets générés ;
- `Cargo.lock` présent localement, ignoré et non suivi ;
- hygiène texte et `git diff --cached --check` propres.
`beta.1.fix.1` est `FAIL` malgré les smokes HTTP `PASS` : son audit Python de stabilisation échoue sur un quoting invalide et `git diff --cached --check` détecte une ligne vide supplémentaire à EOF. La transition autorisée est uniquement `beta.1.fix.2`.
## Gate de `0.0.2-beta.1.fix.2`
Le fix de clôture beta doit prouver :
- `VERSION = 0.0.2-beta.1.fix.2` et version workspace identique ;
- history `history/0.0.2/beta.1.fix.1.md` présent avec statut `FAIL` et committé avant application ;
- aucun diff sous `crates/` ou `Web/` ;
- aucune nouvelle fonctionnalité, package Cargo/npm, dépendance ou contrat runtime ;
- audit Python de stabilisation corrigé et exécutable, sans chaîne littérale mal quotée ;
- fichiers texte gouvernés terminés par exactement un LF, sans ligne vide supplémentaire à EOF ;
- audits Markdown locaux propres ;
- preuves runtime acquises en `beta.1.fix.1` conservées dans son history : Cargo/Clippy/tests, baseline Web et trois smokes HTTP `PASS` ;
- `Cargo.lock` présent localement, ignoré et non suivi ;
- `git diff --check` puis `git diff --cached --check` propres ;
- gate interactive ne modifiant pas le shell opérateur avec `set -euo pipefail`.
Le `PASS` de `beta.1.fix.2`, combiné aux preuves runtime `PASS` conservées dans `history/0.0.2/beta.1.fix.1.md`, ferme la stabilisation beta et autorise uniquement `0.0.2-rc.1`.
## Gate de 0.0.2-rc.1
La release candidate gèle le scope et doit rejouer la preuve complète de release :
```text
CHECK VERSION/Cargo = 0.0.2-rc.1 et history beta.1.fix.2 PASS
AUDIT ensemble exact des deltas/histories 0.0.2 antérieurs
AUDIT aucun diff produit sous crates/ ou Web/
AUDIT Markdown, hygiène texte à un LF et audits Rust locaux
FORMAT cargo fmt --all puis cargo fmt --all -- --check avant audits Rust
CHECK cargo metadata + cargo check workspace
LINT clippy workspace strict -D warnings
TEST intégration Git réelle + tests workspace
BUILD npm ci + typecheck + build du template lab
CHECK manifest Vite de production
SMOKE trois launchers : /healthz, / et assets manifest servis en HTTP
AUDIT Cargo.lock local/ignoré/non suivi, hors-scope absents, aucune 0.0.3 réservée
CHECK git diff --check puis git diff --cached --check
```
Pendant la gate RC, `deltas/0.0.2/rc.1.md` doit être le seul delta `0.0.2` sans history correspondant. Un `PASS` autorise uniquement `rel.1` ; toute régression fonctionnelle importante impose un retour de phase ou un REPLAN.
## Gate de `0.0.2-rel.1`
La fermeture stable pré-tag doit prouver :
- `VERSION = 0.0.2` et version workspace Cargo identique ;
- history `rc.1` `PASS` committé avant application de `rel.1` ;
- `rel.1` comme seul delta `0.0.2` sans history pendant sa gate ;
- changelog stable `## 0.0.2` sans marqueur `candidate` ;
- roadmap avec `(x) 0.0.2` et aucune release postérieure numérotée ;
- README, index documentaire et plan `0.0.2` présentés comme stables/historiques ;
- aucun prompt `0.0.3` inventé ;
- absence de changement sous `crates/`, `Web/`, `docs/architecture/`, `docs/studies/`, `docs/rules/` et `prompts/` ;
- `cargo fmt --all` puis `cargo fmt --all -- --check` avant les audits Rust ;
- audits Rust locaux, `cargo check`, Clippy strict, test Git réel et tests workspace `PASS` ;
- `npm ci`, typecheck, build Vite et manifest de production `PASS` ;
- trois smokes HTTP `PASS` avec `/healthz`, `/` et assets hashés servis ;
- `Cargo.lock` local présent, ignoré et non suivi ;
- fichiers texte gouvernés terminés par exactement un LF ;
- `git diff --check` et `git diff --cached --check` propres ;
- aucun tag stable ou rapport post-tag déclaré comme existant avant le history `rel.1` `PASS` et le commit stable.
## Gate de `0.0.3-alpha.1`
La première tranche de `0.0.3` est une gate de cadrage sans code produit. Elle doit prouver :
- `VERSION = 0.0.3-alpha.1` et `[workspace.package].version` identique ;
- baseline Knowledge `v0.0.2` identifiée par commit `183d354a67bec6d2d512c4898f5189759a6120b0`, SHA-256 `0e518c7eaad115811cccc8fb2b38a1e68150ec985c2ca72ec08a9ca5bbb202ce` et rapport `publish.001 PASS` ;
- aucun chargement/adoption PGR nouveau et PGR `v0.1.3` conservé uniquement comme provenance historique ;
- `ROADMAP.md` engage uniquement `0.0.3` après les stables existantes et ne réserve aucune version ultérieure ;
- étude `006` et plan `003` présents ;
- mission bornée à l'écriture Git contrôlée et au push gardé ;
- contrat d'écriture prévu distinct de `GitRepository` afin de préserver les consommateurs lecture seule ;
- rebase/merge automatique, PostgreSQL, forge, auth/admin et workflow complet de proposition encore hors scope ;
- aucun diff sous `crates/` ou `Web/` ;
- aucun history `history/0.0.3/alpha.1.md` présent avant la gate ;
- aucun nouveau package Cargo ou npm et aucune nouvelle dépendance ;
- audits Markdown et hygiène texte propres ;
- `cargo fmt --all` puis `cargo fmt --all -- --check` exécutés avant les audits Rust ;
- audits Rust locaux propres ;
- `cargo metadata`, `cargo check --workspace`, Clippy strict, test d'intégration Git de lecture et tests workspace propres ;
- `Cargo.lock` généré localement si Cargo l'exige, mais ignoré/non suivi ;
- `package-lock.json` inchangé et versionné ;
- `git diff --cached --check` propre avant commit.
La baseline Web n'est pas rejouée automatiquement dans cette tranche, car aucun fichier sous `Web/` ni contrat runtime Web n'est modifié et la stable `0.0.2` a déjà acquis sa gate complète.
Un `PASS` de `0.0.3-alpha.1` autorise uniquement la préparation de `0.0.3-alpha.2`. Un autre statut doit d'abord être conservé dans son history puis conduire à un fix borné ou à un REPLAN.

View File

@@ -0,0 +1,146 @@
<!-- file: history/0.0.2/alpha.1.fix.1.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.1.fix.1 — restructuration du plan de release
## Statut
PASS
La gate opérateur de `0.0.2-alpha.1.fix.1` exécutée le `2026-09-28` est propre.
## Baseline opérateur
`alpha.1` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.2/alpha.1.md = FAIL
commit ab1e5fd
message 0.0.2-alpha.1
branch master
push origin/master PASS
```
Le push observé a avancé `origin/master` de `98ec46f` à `ab1e5fd`.
## Version et prérequis
Les contrôles passent :
```text
VERSION 0.0.2-alpha.1.fix.1
Cargo workspace version 0.0.2-alpha.1.fix.1
history alpha.1 FAIL
```
`git diff --check` est propre.
## Structure du plan
L'audit dédié passe :
```text
Knowledge 0.0.2-alpha.1.fix.1 plan structure audit: clean
```
Il confirme notamment :
- l'ordre attendu des sections de niveau `##` ;
- `Scope` et `Hors scope` globaux séparés de la trajectoire ;
- les tranches `alpha.1`, `alpha.2`, `alpha.3`, `alpha.4`, `beta.1`, `rc.1` et `rel.1` sous `## Trajectoire prévisionnelle` avec des titres `###` ;
- l'absence de titres de tranche au niveau `##` ;
- la présence des déclencheurs de `REPLAN` ;
- la revue d'horizon explicitement non réalisée ;
- l'absence de réservation d'une version postérieure à `0.0.2`.
Le fix ne modifie aucune décision technique Git prise par l'étude `004`.
## Rust
Les audits structurels passent :
```text
Rust test-layout audit: clean (7 crate(s))
Rust blank-line audit: clean (18 file(s))
Rust item-spacing audit: clean (18 file(s))
Rust module-order audit: clean (18 file(s))
Rust trait-import-block audit: clean (18 file(s))
Rust re-export-block audit: clean (18 file(s))
Rust export completeness audit: 0 candidate(s) across 7 package(s)
```
Les commandes Cargo passent :
```text
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test --workspace --all-targets --all-features PASS
```
Tests observés :
```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
```
## Web
Les commandes suivantes passent :
```text
npm ci
npm run typecheck
npm run build
```
npm observe `43` packages audités et `0` vulnérabilité.
Le warning `@parcel/watcher@2.6.0` relatif à `allowScripts` reste observable sans approbation automatique.
Les warnings de dépréciation Sass provenant de Bootstrap restent non bloquants. Vite transforme `63` modules et termine le build avec succès.
## Scope et hygiène Git
Le contrôle suivant reste vide :
```text
git diff --name-only -- crates Web
```
Le fix n'a donc modifié aucun code Rust ni aucun fichier Web.
Après staging, `git diff --cached --check` passe et le staging observé avant ajout du présent history est exactement :
```text
M Cargo.toml
M VERSION
A deltas/0.0.2/alpha.1.fix.1.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.2-alpha.1.fix.1` est `PASS`.
Le défaut documentaire d'`alpha.1` est fermé sans modification de son scope fonctionnel ni des décisions Git déjà retenues.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.1.fix.1"
-> push origin master
-> vérifier le workspace propre
-> préparer seulement ensuite 0.0.2-alpha.2
```
`alpha.2` peut matérialiser le premier backend Git réel dans `knowledge-git-lib`, dans les limites du plan corrigé.

146
history/0.0.2/alpha.1.md Normal file
View File

@@ -0,0 +1,146 @@
<!-- file: history/0.0.2/alpha.1.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.1 — adoption PGR et cadrage Git/synchronisation
## Statut
FAIL
## Baseline opérateur
La tranche évaluée est :
```text
version 0.0.2-alpha.1
delta deltas/0.0.2/alpha.1.md
date 2026-09-28
```
Le `FAIL` est documentaire. Les commandes techniques transmises par l'opérateur sont propres, mais la revue du plan actif révèle une structure de pilotage incomplète par rapport au plan historique `0.0.1`.
## Preuves opérateur observées
### Version et hygiène initiale
Les contrôles suivants passent :
```text
VERSION = 0.0.2-alpha.1
workspace.package.version = 0.0.2-alpha.1
git diff --check = PASS
```
### Audits Rust structurels
Les sept audits exécutés sont propres :
```text
Rust test-layout audit: clean (7 crate(s))
Rust blank-line audit: clean (18 file(s))
Rust item-spacing audit: clean (18 file(s))
Rust module-order audit: clean (18 file(s))
Rust trait-import-block audit: clean (18 file(s))
Rust re-export-block audit: clean (18 file(s))
Rust export completeness audit: 0 candidate(s) across 7 package(s)
```
### Prérequis Git
L'exécutable observé est :
```text
git version 2.47.3
```
### Cargo, Clippy et tests
Les commandes suivantes passent :
```text
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings
PASS
cargo test --workspace --all-targets --all-features PASS
```
Résultats de tests observés :
```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
```
### Baseline Web
Les commandes suivantes passent :
```text
npm ci PASS
npm run typecheck PASS
npm run build PASS
```
`npm ci` ne rapporte aucune vulnérabilité. Le build Vite réussit avec des warnings de dépréciation Sass provenant de Bootstrap ; ces warnings n'empêchent pas la gate de compilation.
### Scope de la tranche
Les fichiers attendus sont présents :
```text
docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
deltas/0.0.2/alpha.1.md
```
Le history `history/0.0.2/alpha.1.md` était absent pendant la gate, conformément au workflow.
`git diff --name-only -- crates Web` est vide. Aucun code Rust ou frontend n'a donc été modifié par `alpha.1`.
`git diff --cached --check` est propre.
Le statut transmis avant création du présent history contient uniquement les fichiers documentaires/configuration annoncés par `alpha.1`.
## Défaut documentaire observé
La revue manuelle du plan `docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md` établit que le document ne reprend pas plusieurs éléments structurants déjà présents dans `docs/plans/001-V0_0_1_FOUNDATION_PLAN.md` :
1. les tranches `alpha.*`, `beta.1`, `rc.1` et `rel.1` sont déclarées comme sections de niveau `##` au lieu d'être des sous-sections `###` de `## Trajectoire prévisionnelle` ;
2. la section globale `## Scope` manque ;
3. la section `## État acquis et questions ouvertes` manque ;
4. la section `## Déclencheurs de REPLAN` manque ;
5. la section `## Revue d'horizon` manque ;
6. l'ordre général du document ne suit pas le patron de pilotage éprouvé par le plan `0.0.1`.
Ces défauts diminuent la lisibilité du forecast et surtout la capacité à distinguer mission, scope global, trajectoire par tranche et conditions imposant une replanification.
Le contenu technique de l'étude Git et les décisions déjà prises ne sont pas invalidés par ce défaut de structure.
## Contrôles non nécessaires pour établir le FAIL
Les preuves complémentaires de manifest Vite et les smokes HTTP ne figurent pas dans le résultat opérateur transmis avec cette gate. Elles ne sont pas inventées ni déclarées exécutées.
Le défaut documentaire ci-dessus suffit à classer la tranche `FAIL` avant sa clôture.
## Diagnostic
`alpha.1` doit être corrigée sans avancer vers l'implémentation Git :
- conserver le delta `alpha.1` et le présent history comme artefacts historiques ;
- ne pas modifier les décisions techniques de l'étude `004` sans nouvelle preuve ;
- restructurer le plan `0.0.2` sur le patron de `0.0.1` ;
- ajouter une gate explicite de structure du plan pour éviter la régression ;
- rester hors de `crates/` et `Web/`.
## Transition
Après application du présent history :
1. committer et pousser l'état complet `0.0.2-alpha.1` avec son résultat `FAIL` ;
2. appliquer ensuite seulement `0.0.2-alpha.1.fix.1` ;
3. ne pas préparer `alpha.2` tant que le fix n'est pas `PASS`.

View File

@@ -0,0 +1,193 @@
<!-- file: history/0.0.2/alpha.2.fix.1.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.2.fix.1 — correction de gate, formatage et construction interne
## Statut
FAIL
La gate opérateur de `0.0.2-alpha.2.fix.1` exécutée le `2026-09-28` corrige les défauts initiaux d'`alpha.2`, mais reste bloquée par une violation Clippy unique dans un test de `knowledge-git-lib`.
## Baseline opérateur
`alpha.2` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.2/alpha.2.md FAIL
commit bdbd6a5
message 0.0.2-alpha.2
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `84c01eb` à `bdbd6a5`.
## Version et prérequis
Les contrôles passent :
```text
VERSION 0.0.2-alpha.2.fix.1
Cargo workspace version 0.0.2-alpha.2.fix.1
history alpha.2 FAIL
```
`git diff --check` est propre.
## Audit structurel et documentaire
L'audit corrigé passe :
```text
Knowledge 0.0.2-alpha.2.fix.1 structural audit: clean (61 Markdown file(s))
```
Il confirme notamment :
- la présence des artefacts attendus de `knowledge-git-lib` ;
- huit membres workspace avec dépendances minimales inchangées ;
- `GitSourceKey` construit via `Self(value)` tout en conservant son champ tuple privé ;
- le runner process sans shell, les protections d'environnement Git et les arguments `--mirror` / `--no-local` ;
- l'absence de `refresh`, `resolve_revision`, worktree/snapshot et `knowledge-app-lib` ;
- l'exclusion de `.git`, `node_modules`, `target` et `builds` de l'audit Markdown gouverné.
Les audits Rust structurels passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (22 file(s))
Rust item-spacing audit: clean (22 file(s))
Rust module-order audit: clean (22 file(s))
Rust trait-import-block audit: clean (22 file(s))
Rust re-export-block audit: clean (22 file(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
## Formatage et compilation
Les contrôles suivants passent :
```text
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
```
La compilation du workspace est donc restaurée par rapport à `alpha.2`.
## Clippy
La gate reste `FAIL` sur :
```text
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Erreur observée :
```text
crates/knowledge-git-lib/unit_tests/backend.rs:85
missing `return` statement
clippy::implicit_return
```
La closure passée à `Iterator::all` retourne implicitement son expression booléenne :
```text
|character| character.is_ascii_alphanumeric() || character == '-'
```
La configuration workspace interdit explicitement `clippy::implicit_return`. Le correctif attendu est uniquement de rendre ce retour explicite dans la closure.
## Tests
Malgré l'échec Clippy, les tests ciblés passent :
```text
cargo test -p knowledge-git-lib --all-targets --all-features
7 passed; 0 failed
```
Tests observés :
```text
backend_configuration_requires_non_empty_paths PASS
local_source_clone_uses_mirror_no_local_and_argument_separator PASS
invalid_existing_mirror_is_rejected_without_deleting_it PASS
existing_mirror_is_inspected_without_clone PASS
source_key_never_interpolates_raw_source_identifier_as_a_path PASS
process_errors_do_not_expose_remote_location_or_credentials PASS
remote_source_clone_does_not_add_no_local PASS
```
Les tests workspace passent également :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-git-lib : 7 PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
## Métadonnées, lockfile et frontières
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.2.fix.1 Cargo metadata audit: clean
```
Les contrôles de frontière passent :
```text
Web/ sans diff
crates/knowledge-git-api sans diff
crates/knowledge-sync-api sans diff
crates/knowledge-app-lib absent
crates/knowledge-forge-gitea-lib absent
crates/knowledge-store-postgres-lib absent
Cargo.lock présent localement, ignoré et non suivi
```
Après staging, `git diff --cached --check` passe. Le staging observé avant ajout du présent history est :
```text
M Cargo.toml
M VERSION
M crates/knowledge-git-lib/src/backend.rs
M crates/knowledge-git-lib/src/error.rs
M crates/knowledge-git-lib/src/lib.rs
M crates/knowledge-git-lib/unit_tests/backend.rs
A deltas/0.0.2/alpha.2.fix.1.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Diagnostic
`alpha.2.fix.1` reste dans le scope prévu et restaure l'hygiène, le formatage et la compilation. Aucun déclencheur de REPLAN n'est atteint.
Le nouveau fix doit uniquement :
- rendre explicite le retour de la closure signalée par Clippy dans `unit_tests/backend.rs` ;
- conserver l'API, les dépendances et le comportement Git inchangés ;
- rejouer la gate complète d'`alpha.2` afin de confirmer qu'aucun autre warning Clippy n'est masqué.
## Décision
`0.0.2-alpha.2.fix.1` est `FAIL`.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.2.fix.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.2.fix.2
```

View File

@@ -0,0 +1,164 @@
<!-- file: history/0.0.2/alpha.2.fix.2.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.2.fix.2 — clôture du backend process minimal
## Statut
PASS
La gate opérateur de `0.0.2-alpha.2.fix.2` exécutée le `2026-09-28` est propre et ferme la tranche fonctionnelle `alpha.2`.
## Baseline opérateur
`alpha.2.fix.1` `FAIL` a été historisé, committé et poussé avant application du second fix :
```text
history/0.0.2/alpha.2.fix.1.md FAIL
commit ffb4721
message 0.0.2-alpha.2.fix.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `bdbd6a5` à `ffb4721`.
## Version et prérequis
Les contrôles passent :
```text
VERSION 0.0.2-alpha.2.fix.2
Cargo workspace version 0.0.2-alpha.2.fix.2
history alpha.2.fix.1 FAIL
```
`git diff --check` est propre.
## Audit structurel
L'audit dédié passe :
```text
Knowledge 0.0.2-alpha.2.fix.2 structural audit: clean
```
Il confirme notamment :
- huit membres Cargo exactement ;
- `knowledge-git-lib` présent et dépendant uniquement de `knowledge-domain-lib` dans cette tranche ;
- retour explicite dans la closure de validation du `source-key` ;
- absence de `refresh`, `resolve_revision`, worktree/snapshot et `knowledge-app-lib` ;
- absence de dérive fonctionnelle dans `backend.rs`, `error.rs` et `lib.rs`.
Les audits Rust structurels passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (22 file(s))
Rust item-spacing audit: clean (22 file(s))
Rust module-order audit: clean (22 file(s))
Rust trait-import-block audit: clean (22 file(s))
Rust re-export-block audit: clean (22 file(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
## Formatage, compilation et Clippy
Toutes les gates passent :
```text
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
```
Le défaut `clippy::implicit_return` observé dans `alpha.2.fix.1` est donc fermé sans modification fonctionnelle du backend.
## Tests
Les tests ciblés de `knowledge-git-lib` passent :
```text
cargo test -p knowledge-git-lib --all-targets --all-features
7 passed; 0 failed
```
Tests observés :
```text
backend_configuration_requires_non_empty_paths PASS
source_key_never_interpolates_raw_source_identifier_as_a_path PASS
invalid_existing_mirror_is_rejected_without_deleting_it PASS
existing_mirror_is_inspected_without_clone PASS
process_errors_do_not_expose_remote_location_or_credentials PASS
local_source_clone_uses_mirror_no_local_and_argument_separator PASS
remote_source_clone_does_not_add_no_local PASS
```
Les tests workspace passent également :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-git-lib : 7 PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
## Métadonnées, lockfile et frontières
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.2.fix.2 Cargo metadata audit: clean
```
Les contrôles de frontière passent :
```text
Web/ sans diff
crates/knowledge-git-api sans diff
crates/knowledge-sync-api sans diff
crates/knowledge-git-lib/src/backend.rs sans diff dans fix.2
crates/knowledge-git-lib/src/error.rs sans diff dans fix.2
crates/knowledge-git-lib/src/lib.rs sans diff dans fix.2
crates/knowledge-app-lib absent
crates/knowledge-forge-gitea-lib absent
crates/knowledge-store-postgres-lib absent
Cargo.lock présent localement, ignoré et non suivi
```
Après staging, `git diff --cached --check` passe. Le staging observé avant ajout du présent history est :
```text
M Cargo.toml
M VERSION
M crates/knowledge-git-lib/unit_tests/backend.rs
A deltas/0.0.2/alpha.2.fix.2.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.2-alpha.2.fix.2` est `PASS`.
La tranche `alpha.2` est fermée. Le backend possède maintenant sa configuration physique, sa racine d'état, sa clé de stockage sûre, son runner process sans shell et l'initialisation/inspection minimale du mirror.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.2.fix.2"
-> push origin master
-> vérifier le workspace propre
-> préparer seulement ensuite 0.0.2-alpha.3
```
`alpha.3` peut ajouter le refresh réel, la résolution vers commit et le snapshot/worktree déterministe sur repositories Git temporaires locaux.

160
history/0.0.2/alpha.2.md Normal file
View File

@@ -0,0 +1,160 @@
<!-- file: history/0.0.2/alpha.2.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.2 — contrats minimaux et backend process Git
## Statut
FAIL
La gate opérateur de `0.0.2-alpha.2` exécutée le `2026-09-28` échoue avant validation fonctionnelle du nouveau backend Git.
## Baseline opérateur
`alpha.1.fix.1` a été historisé `PASS`, committé et poussé avant application d'`alpha.2` :
```text
history/0.0.2/alpha.1.fix.1.md PASS
commit 84c01eb
message 0.0.2-alpha.1.fix.1
branch master
push origin/master PASS
```
Le push observé a avancé `origin/master` de `ab1e5fd` à `84c01eb`.
## Version et contrôles initiaux
Les contrôles de version passent :
```text
VERSION 0.0.2-alpha.2
Cargo workspace version 0.0.2-alpha.2
history alpha.1.fix.1 PASS
```
En revanche, l'hygiène Git échoue immédiatement :
```text
git diff --check
docs/validation/001-VALIDATION_MATRIX.md:574: new blank line at EOF.
```
Ce défaut est bloquant pour la tranche.
## Audit structurel du delta
L'audit structurel embarqué échoue sur un fichier généré sous `node_modules` :
```text
AssertionError: Web/knowledge-template-lab/node_modules/sync-child-process/README.md
```
Le défaut vient de la gate elle-même : elle parcourt récursivement tous les `*.md` du workspace et n'exclut que `.git`, alors que `node_modules` ne fait pas partie du corpus Markdown gouverné par Knowledge.
L'intention de contrôle des headers Markdown reste valide, mais la gate doit ignorer les arbres générés/non gouvernés tels que `node_modules`.
## Audits Rust structurels
Les audits structurels passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (22 file(s))
Rust item-spacing audit: clean (22 file(s))
Rust module-order audit: clean (22 file(s))
Rust trait-import-block audit: clean (22 file(s))
Rust re-export-block audit: clean (22 file(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
Ils confirment la présence cohérente de la huitième crate, mais ne remplacent pas `cargo fmt`, la compilation, Clippy et les tests.
## Formatage Rust
`cargo fmt --all -- --check` échoue sur les nouveaux fichiers de `knowledge-git-lib`.
Les écarts sont uniquement des différences de formatage rustfmt dans :
```text
crates/knowledge-git-lib/src/backend.rs
crates/knowledge-git-lib/src/error.rs
crates/knowledge-git-lib/src/lib.rs
crates/knowledge-git-lib/unit_tests/backend.rs
```
La tranche n'est donc pas formatée conformément à la gate Rust du projet.
## Compilation et tests
`cargo metadata --format-version 1 --no-deps` produit correctement les métadonnées attendues, mais `cargo check --workspace` échoue avec `E0423` :
```text
crates/knowledge-git-lib/src/backend.rs:126
return crate::GitSourceKey(value);
```
`GitSourceKey` est réexporté publiquement depuis `crate`, mais son champ tuple reste privé au module `backend`. La construction doit donc être effectuée depuis le module propriétaire, par exemple via `crate::backend::GitSourceKey(value)`, ou par un constructeur privé équivalent.
Le même défaut bloque :
```text
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --all-targets --all-features
cargo test --workspace --all-targets --all-features
```
Aucun résultat de test `knowledge-git-lib` n'est donc présenté comme exécuté avec succès.
## Métadonnées et limites de scope
L'audit des métadonnées Cargo passe :
```text
Knowledge 0.0.2-alpha.2 Cargo metadata audit: clean
```
Il confirme huit packages exactement, dont `knowledge-git-lib`.
Les contrôles de frontière passent également :
```text
Web/ sans diff
crates/knowledge-git-api sans diff
crates/knowledge-sync-api sans diff
crates/knowledge-app-lib absent
crates/knowledge-forge-gitea-lib absent
crates/knowledge-store-postgres-lib absent
Cargo.lock ignoré et non suivi
```
Le `git status --short` final n'a pas été exécuté à cause d'une faute de frappe opérateur (`it status --short`). Cette faute de frappe n'influence pas la décision : `git diff --cached --check` venait déjà d'échouer sur la ligne vide finale de la matrice de validation.
## Diagnostic
`alpha.2` reste dans son scope prévu ; aucun REPLAN n'est nécessaire.
Le correctif doit uniquement :
- supprimer la ligne vide terminale fautive de la matrice de validation ;
- corriger la gate Markdown pour exclure les arbres générés/non gouvernés ;
- appliquer le formatage rustfmt attendu aux quatre fichiers Rust signalés ;
- corriger la construction privée de `GitSourceKey` sans élargir l'API publique ;
- conserver inchangés le scope, les dépendances et les responsabilités du backend ;
- ne pas introduire `refresh`, résolution de révision ou snapshot fonctionnel avant `alpha.3`.
## Décision
`0.0.2-alpha.2` est `FAIL`.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.2"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.2.fix.1
```

View File

@@ -0,0 +1,173 @@
<!-- file: history/0.0.2/alpha.3.fix.1.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.3.fix.1 — clôture du chemin Git réel et REPLAN d'autonomie
## Statut
PASS
La gate opérateur de `0.0.2-alpha.3.fix.1` exécutée le `2026-09-28` est propre. Elle ferme les défauts de conformité d'`alpha.3`, conserve son comportement Git réel et valide le REPLAN qui insère `alpha.4` avant le cas d'usage de synchronisation.
## Baseline opérateur
`alpha.3` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.2/alpha.3.md FAIL
commit 721e8a6
message 0.0.2-alpha.3
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `3802526` à `721e8a6`.
## Version et gouvernance
Les contrôles passent :
```text
VERSION 0.0.2-alpha.3.fix.1
Cargo workspace version 0.0.2-alpha.3.fix.1
history alpha.3 FAIL
structural/replan audit clean
```
Le plan `0.0.2` contient désormais la trajectoire :
```text
alpha.3 refresh / résolution / snapshot réel
alpha.4 autonomie normative + structure interne des crates
alpha.5 cas d'usage de synchronisation
beta.1 réconciliation + revue d'horizon
rc.1 candidate stable
rel.1 fermeture stable
```
PGR `v0.1.3` est enregistré comme provenance d'adoption figée. Une version, session ou tranche Knowledge ne doit pas consulter automatiquement une nouvelle stable PGR ; une comparaison future exige une réévaluation PGR explicitement engagée.
## Audits structurels
`git diff --check` passe.
Les audits Rust structurels passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (23 file(s))
Rust item-spacing audit: clean (23 file(s))
Rust module-order audit: clean (23 file(s))
Rust trait-import-block audit: clean (23 file(s))
Rust re-export-block audit: clean (23 file(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
Les contrôles de scope confirment notamment :
```text
Web/ sans diff
crates/knowledge-sync-api sans diff
crates/knowledge-git-api/src sans diff
crates/knowledge-git-lib/src sans diff
prompts/ sans diff
docs/rules/ absent avant alpha.4
```
Le fix n'a donc pas absorbé prématurément le travail transversal réservé à `alpha.4`.
## Formatage, compilation et Clippy
Toutes les gates passent :
```text
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
```
Les défauts initiaux d'`alpha.3` sont fermés : fin de fichier de la matrice propre, formatage du test `knowledge-git-api` conforme et documentation de crate du test d'intégration présente.
## Tests Git ciblés
Le contrat `knowledge-git-api` passe :
```text
cargo test -p knowledge-git-api --all-targets --all-features
1 passed; 0 failed
```
`knowledge-git-lib` passe avec huit tests unitaires et deux tests d'intégration réels :
```text
cargo test -p knowledge-git-lib --all-targets --all-features
8 unit passed; 0 failed
2 integration passed; 0 failed
```
Le test d'intégration ciblé passe également :
```text
cargo test -p knowledge-git-lib --test git_read_path
2 passed; 0 failed
```
Les scénarios réels continuent de prouver refresh, résolution, snapshots par révision, second commit source, référence invalide et rejet d'un snapshot sale.
## Tests workspace
La suite workspace passe intégralement :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-git-lib : 8 unit PASS + 2 integration PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
## Métadonnées et lockfile
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.3.fix.1 Cargo metadata audit: clean
```
Les huit packages attendus sont présents. `Cargo.lock` est présent localement, ignoré et non suivi.
Après staging, `git diff --cached --check` passe. Le staging observé avant ajout du présent history est :
```text
M Cargo.toml
M README.md
M RULES.md
M VERSION
M crates/knowledge-git-api/unit_tests/lib.rs
M crates/knowledge-git-lib/tests/git_read_path.rs
A deltas/0.0.2/alpha.3.fix.1.md
M docs/architecture/001-PROJECT_PROFILE.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.2-alpha.3.fix.1` est `PASS`.
La tranche `alpha.3` est fermée. La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.3.fix.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.4
```
`alpha.4` peut maintenant terminer l'autonomie normative Knowledge et normaliser les crate-roots sans modifier la sémantique Git validée.

171
history/0.0.2/alpha.3.md Normal file
View File

@@ -0,0 +1,171 @@
<!-- file: history/0.0.2/alpha.3.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.3 — refresh, résolution et snapshot Git réels
## Statut
FAIL
La gate opérateur de `0.0.2-alpha.3` exécutée le `2026-09-28` confirme le comportement fonctionnel du chemin Git réel, mais la tranche reste bloquée par trois défauts de conformité : hygiène Git, formatage rustfmt et documentation de la crate de test d'intégration sous Clippy strict.
## Baseline opérateur
`alpha.2.fix.2` a été historisé `PASS`, committé et poussé avant application d'`alpha.3` :
```text
history/0.0.2/alpha.2.fix.2.md PASS
commit 3802526
message 0.0.2-alpha.2.fix.2
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `ffb4721` à `3802526`.
## Version et audit structurel
Les contrôles initiaux passent :
```text
VERSION 0.0.2-alpha.3
Cargo workspace version 0.0.2-alpha.3
history alpha.2.fix.2 PASS
Knowledge 0.0.2-alpha.3 structural audit: clean
```
Les audits Rust structurels passent également :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (23 file(s))
Rust item-spacing audit: clean (23 file(s))
Rust module-order audit: clean (23 file(s))
Rust trait-import-block audit: clean (23 file(s))
Rust re-export-block audit: clean (23 file(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
## Hygiène Git et formatage
`git diff --check` échoue sur :
```text
docs/validation/001-VALIDATION_MATRIX.md:639: new blank line at EOF.
```
`cargo fmt --all -- --check` échoue uniquement sur `crates/knowledge-git-api/unit_tests/lib.rs`, où rustfmt compacte l'assertion finale `GitSnapshot` sur une seule ligne.
Ces deux défauts sont bloquants même si la compilation réussit ensuite.
## Compilation et Clippy
`cargo metadata --format-version 1 --no-deps` et `cargo check --workspace` passent.
Clippy strict échoue sur :
```text
crates/knowledge-git-lib/tests/git_read_path.rs
missing documentation for the crate
-D missing-docs
```
Le test d'intégration constitue une crate Cargo indépendante ; il doit donc posséder une documentation de crate explicite compatible avec la politique `missing_docs` du workspace.
## Tests fonctionnels Git
Le contrat provider-neutral compile et son test passe :
```text
cargo test -p knowledge-git-api --all-targets --all-features
1 passed; 0 failed
```
`knowledge-git-lib` exécute huit tests unitaires et deux tests d'intégration avec de vrais repositories Git temporaires :
```text
8 unit tests PASS
2 integration tests PASS
```
Les preuves observées couvrent notamment :
- enregistrement de source et rejet des doublons ;
- refresh du mirror réel ;
- résolution de `refs/heads/main` ;
- matérialisation d'un snapshot par révision ;
- second commit source puis nouvelle révision/snapshot ;
- rejet d'une référence inexistante ;
- rejet d'un snapshot existant rendu sale.
Le test d'intégration ciblé `cargo test -p knowledge-git-lib --test git_read_path` passe également avec `2 passed; 0 failed`.
Les tests workspace passent intégralement malgré le warning `missing_docs` non promu en erreur par `cargo test` :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-git-lib : 8 unit PASS + 2 integration PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
## Métadonnées et frontières
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.3 Cargo metadata audit: clean
```
Les contrôles de frontière passent :
```text
Web/ sans diff
crates/knowledge-sync-api sans diff
crates/knowledge-app-lib absent
crates/knowledge-forge-gitea-lib absent
crates/knowledge-store-postgres-lib absent
history/0.0.2/alpha.3.md absent avant clôture
Cargo.lock présent localement, ignoré et non suivi
```
Le `git diff --cached --check` final échoue pour la même ligne vide terminale déjà signalée dans la matrice. La commande finale `git status --short` n'a pas été exécutée en raison d'une faute de frappe opérateur (`it status --short`), sans effet sur la décision déjà acquise.
## Replanification décidée
Pendant la revue de la tranche, deux besoins structurels ont été confirmés sans remettre en cause les primitives Git d'`alpha.3` :
1. Knowledge doit devenir autonome après son adoption PGR : seules les règles réellement retenues doivent être adaptées et matérialisées localement ; PGR `v0.1.3` reste une provenance d'adoption et ne doit pas être rechargé automatiquement au début de chaque version, session ou tranche.
2. Les crates Rust doivent être auditées afin que `lib.rs` reste une façade de crate et que les définitions soient possédées par des modules nommés selon leur responsabilité lorsque la taille/structure le justifie, en particulier pour les crates `-api`.
Ces sujets ne corrigent pas le comportement Git d'`alpha.3` et ne doivent donc pas être absorbés dans son implémentation. Le plan `0.0.2` est replanifié pour insérer une tranche `alpha.4` dédiée à cette autonomie normative et à l'organisation interne des crates ; l'ancien cas d'usage de synchronisation devient `alpha.5`.
## Diagnostic
`alpha.3` est fonctionnellement probante mais ne satisfait pas la gate complète.
Le correctif doit uniquement :
- supprimer la ligne vide terminale fautive de la matrice ;
- appliquer le formatage rustfmt attendu au test de `knowledge-git-api` ;
- ajouter la documentation de crate requise au test d'intégration `git_read_path` ;
- enregistrer explicitement le REPLAN `alpha.4` / `alpha.5` et la règle d'autonomie PGR ;
- ne pas élargir le comportement Git déjà validé par les tests.
## Décision
`0.0.2-alpha.3` est `FAIL`.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.3"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.3.fix.1
```

View File

@@ -0,0 +1,134 @@
<!-- file: history/0.0.2/alpha.4.fix.1.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.4.fix.1 — fermeture de l'autonomie normative
## Statut
PASS
La gate opérateur de `0.0.2-alpha.4.fix.1` exécutée le `2026-09-28` est propre. Elle corrige les deux constructeurs privés de `knowledge-git-api` sans modifier l'API publique, les owners locaux, les templates, les audits ni le comportement Git validé.
## Baseline opérateur
`alpha.4` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.2/alpha.4.md FAIL
commit 3f8c795
message 0.0.2-alpha.4
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `b9508b0` à `3f8c795`.
## Version et correctif
Les contrôles passent :
```text
VERSION 0.0.2-alpha.4.fix.1
Cargo workspace version 0.0.2-alpha.4.fix.1
history alpha.4 FAIL
constructor/facade audit clean
```
`GitReference::new` et `GitRevision::new` construisent désormais leurs tuple structs privés avec `Self(...)` depuis leurs modules propriétaires. Les champs restent privés et les réexports crate-root restent inchangés.
## Formatage, compilation et Clippy
La gate opérateur exécute explicitement le formatter avant sa vérification :
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
```
## Tests ciblés et workspace
Les suites ciblées passent :
```text
knowledge-git-api 1 PASS
knowledge-sync-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
```
Les deux tests d'intégration Git réels continuent de passer et prouvent les comportements de refresh, résolution, snapshot, référence invalide et snapshot sale.
La suite workspace passe intégralement :
```text
knowledge-domain-lib : 3 PASS
knowledge-git-api : 1 PASS
knowledge-git-lib : 8 unit PASS + 2 integration PASS
knowledge-sync-api : 1 PASS
knowledge-web-lib : 4 PASS
launchers : 0 test chacun
```
## Audits Rust
Après formatage, les audits Rust passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (29 file(s))
Rust item-spacing audit: clean (29 file(s))
Rust module-order audit: clean (29 file(s))
Rust trait-import-block audit: clean (29 file(s))
Rust re-export-block audit: clean (29 file(s))
Rust crate-root facade audit: clean (5 crate root(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
Cette exécution confirme la règle à rendre durable : `cargo fmt --all`, puis `cargo fmt --all -- --check`, doivent précéder les audits du code Rust.
## Métadonnées et frontières
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.4.fix.1 Cargo metadata audit: clean
```
Les huit packages attendus sont présents. `Cargo.lock` est présent localement, ignoré et non suivi.
Les contrôles de frontière passent :
```text
Web/ sans diff
crates/knowledge-git-lib/src sans diff
crates/knowledge-sync-api sans diff
knowledge-git-api lib.rs/repository.rs/snapshot.rs sans diff
knowledge-app-lib absent
knowledge-forge-gitea-lib absent
knowledge-store-postgres-lib absent
```
Après staging, `git diff --cached --check` passe.
## Décision
`0.0.2-alpha.4.fix.1` est `PASS`.
La tranche `alpha.4` est fermée. L'adoption PGR `v0.1.3` est terminée comme migration ponctuelle et Knowledge possède désormais ses règles de travail localement.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.4.fix.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.5
```
`alpha.5` peut matérialiser le cas d'usage partagé de synchronisation sans recharger PGR.

144
history/0.0.2/alpha.4.md Normal file
View File

@@ -0,0 +1,144 @@
<!-- file: history/0.0.2/alpha.4.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.4 — autonomie normative et façades de crates
## Statut
FAIL
La gate opérateur de `0.0.2-alpha.4` exécutée le `2026-09-28` valide l'autonomie documentaire, les audits locaux et la structure des façades, mais la tranche ne compile pas à cause de deux constructions de tuple structs privés depuis leurs réexports crate-root.
## Baseline opérateur
`alpha.3.fix.1` `PASS` a été historisé, committé et poussé avant application d'`alpha.4` :
```text
history/0.0.2/alpha.3.fix.1.md PASS
commit b9508b0
message 0.0.2-alpha.3.fix.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `721e8a6` à `b9508b0`.
## Version et autonomie normative
Les contrôles de version et de provenance passent :
```text
VERSION 0.0.2-alpha.4
Cargo workspace version 0.0.2-alpha.4
history alpha.3.fix.1 PASS
```
Les audits Markdown possédés localement passent :
```text
Markdown header audit: clean (80 file(s))
Markdown link audit: clean (71 local link(s), 80 file(s))
Markdown table audit: clean (4 table(s), 80 file(s))
```
L'audit d'autonomie/structure passe :
```text
Knowledge 0.0.2-alpha.4 autonomy/structure audit: clean
```
Les owners locaux `200`, `210`, `300`, `400`, `510` et `530-WEB_FRONTEND`, le mapping d'adoption, les templates locaux et les audits Markdown/Rust attendus sont présents. PGR `v0.1.3` reste une provenance d'adoption et aucune consultation PGR automatique n'est requise.
## Audits Rust et formatage
Les audits structurels passent :
```text
Rust test-layout audit: clean (8 crate(s))
Rust blank-line audit: clean (29 file(s))
Rust item-spacing audit: clean (29 file(s))
Rust module-order audit: clean (29 file(s))
Rust trait-import-block audit: clean (29 file(s))
Rust re-export-block audit: clean (29 file(s))
Rust crate-root facade audit: clean (5 crate root(s))
Rust export completeness audit: 0 candidate(s) across 8 package(s)
```
`cargo fmt --all -- --check` passe.
## Compilation et Clippy
`cargo check --workspace` échoue dans `knowledge-git-api` sur deux erreurs `E0423` :
```text
crates/knowledge-git-api/src/reference.rs:11
return crate::GitReference(value.to_owned());
crates/knowledge-git-api/src/revision.rs:11
return crate::GitRevision(value.to_owned());
```
Après déplacement des types dans leurs modules propriétaires, leurs champs tuple privés ne sont plus constructibles via le réexport crate-root. Les deux implémentations doivent construire leur propre type avec `Self(...)`.
Clippy strict échoue pour les mêmes deux erreurs de compilation. Aucun assouplissement de visibilité ou de lint n'est justifié.
## Tests
`knowledge-sync-api` compile et son test passe :
```text
cargo test -p knowledge-sync-api --all-targets --all-features
1 passed; 0 failed
```
Les suites `knowledge-git-api`, `knowledge-git-lib` et workspace sont bloquées par la compilation de `knowledge-git-api` et ne peuvent donc pas valider la tranche.
## Métadonnées et frontières
L'audit Cargo passe :
```text
Knowledge 0.0.2-alpha.4 Cargo metadata audit: clean
```
Les huit packages attendus sont présents. `Cargo.lock` est présent localement, ignoré et non suivi.
Les contrôles de frontière passent :
```text
Web/ sans diff
crates/knowledge-git-lib/src sans diff
crates/knowledge-app-lib absent
crates/knowledge-forge-gitea-lib absent
crates/knowledge-store-postgres-lib absent
history/0.0.2/alpha.4.md absent avant clôture
```
Le staging final est propre à la tranche et `git diff --cached --check` passe.
## Diagnostic
L'architecture `alpha.4` n'est pas remise en cause. Le défaut est local aux constructeurs des deux tuple structs après séparation de `lib.rs` en modules propriétaires.
Le correctif doit uniquement :
- construire `GitReference` avec `Self(value.to_owned())` dans son propre `impl` ;
- construire `GitRevision` avec `Self(value.to_owned())` dans son propre `impl` ;
- conserver l'API publique crate-root inchangée ;
- rejouer la gate complète `alpha.4` sans modifier les autres owners, templates, audits ou comportements Git/sync.
## Décision
`0.0.2-alpha.4` est `FAIL`.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.4"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-alpha.4.fix.1
```

158
history/0.0.2/alpha.5.md Normal file
View File

@@ -0,0 +1,158 @@
<!-- file: history/0.0.2/alpha.5.md -->
<!-- version: 1 -->
# History 0.0.2-alpha.5 — cas d'usage partagé de synchronisation
## Statut
PASS
La gate opérateur de `0.0.2-alpha.5` exécutée le `2026-09-28` est propre. La tranche matérialise `knowledge-app-lib` comme owner du cas d'usage partagé de synchronisation de lecture et rend durable l'ordre de validation Rust `cargo fmt --all` puis `cargo fmt --all -- --check` avant les audits du code Rust.
## Baseline opérateur
`alpha.4.fix.1` `PASS` a été historisé, committé et poussé avant application de `alpha.5` :
```text
history/0.0.2/alpha.4.fix.1.md PASS
commit 1fe39df
message 0.0.2-alpha.4.fix.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `3f8c795` à `1fe39df`.
## Version, documentation et formatage
Les contrôles passent :
```text
VERSION 0.0.2-alpha.5
Cargo workspace version 0.0.2-alpha.5
git diff --check PASS
Markdown header audit clean (84 file(s))
Markdown link audit clean (71 local link(s), 84 file(s))
Markdown table audit clean (4 table(s), 84 file(s))
cargo fmt --all PASS
cargo fmt --all -- --check PASS
```
Le formatter est exécuté avant les audits Rust, conformément au contrat local désormais possédé par Knowledge.
## Audits Rust
Après formatage, les audits Rust passent :
```text
Rust test-layout audit: clean (9 crate(s))
Rust blank-line audit: clean (32 file(s))
Rust item-spacing audit: clean (32 file(s))
Rust module-order audit: clean (32 file(s))
Rust trait-import-block audit: clean (32 file(s))
Rust re-export-block audit: clean (32 file(s))
Rust crate-root facade audit: clean (6 crate root(s))
Rust export completeness audit: 0 candidate(s) across 9 package(s)
```
## Compilation et Clippy
Les commandes suivantes passent :
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
```
Les neuf packages attendus sont détectés, dont le nouveau `knowledge-app-lib`.
## Cas d'usage de synchronisation
`knowledge-app-lib` compose les contrats existants sans introduire de persistance, de scheduler ou de forge :
```text
RepositorySyncSignal
-> GitRepository::refresh
-> GitRepository::resolve_revision
-> comparaison previous_revision
-> RepositorySyncOutcome::Unchanged
ou
-> GitRepository::prepare_snapshot
-> RepositorySyncOutcome::Changed
```
Les trois tests ciblés passent :
```text
changed_revision_prepares_snapshot_and_returns_observed_revision PASS
unchanged_revision_does_not_prepare_snapshot PASS
periodic_signal_uses_the_same_reconciliation_path PASS
```
La cause `Periodic` emprunte donc le même cas d'usage que la synchronisation explicite sans matérialiser de scheduler concret.
## Tests ciblés et workspace
Les suites ciblées passent :
```text
knowledge-app-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
```
La suite workspace passe intégralement :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les deux tests d'intégration Git réels continuent de prouver refresh, résolution, snapshots, référence invalide et snapshot sale.
## Contrôles structurels et frontières
L'audit spécifique passe :
```text
Knowledge 0.0.2-alpha.5 sync/format-order audit: clean
```
Il confirme notamment :
- neuf packages Cargo exactement ;
- `knowledge-app-lib` dépend uniquement de `knowledge-git-api` et `knowledge-sync-api` en production ;
- `knowledge-domain-lib` reste uniquement une dépendance de test pour cette crate ;
- `Changed` / `Unchanged`, `previous_revision`, refresh, résolution et snapshot sont matérialisés ;
- aucune dépendance PostgreSQL, Gitea/GitHub/GitLab, Actix, Tokio ou scheduler dans le cas d'usage ;
- `cargo fmt --all` précède les audits Rust dans les owners locaux de validation.
`Cargo.lock` est présent localement, ignoré et non suivi. `Web/` reste sans diff. Aucun adapter forge ni store PostgreSQL n'est créé.
Après staging, `git diff --cached --check` passe.
## Décision
`0.0.2-alpha.5` est `PASS`.
Le scope fonctionnel alpha de `0.0.2` est fermé. La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-alpha.5"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-beta.1
```
`beta.1` doit rester une tranche de réconciliation, stabilisation et revue d'horizon sans grande fonctionnalité nouvelle.

View File

@@ -0,0 +1,170 @@
<!-- file: history/0.0.2/beta.1.fix.1.md -->
<!-- version: 1 -->
# History 0.0.2-beta.1.fix.1 — clôture de stabilisation runtime
## Statut
FAIL
La gate opérateur de `0.0.2-beta.1.fix.1` exécutée le `2026-09-29` acquiert les preuves Rust, Cargo, tests, Web et les trois smokes HTTP requis, mais deux commandes requises échouent dans la gate elle-même : l'audit Python de stabilisation contient une chaîne mal quotée et `git diff --cached --check` détecte une ligne vide supplémentaire en fin de `docs/validation/001-VALIDATION_MATRIX.md`.
Conformément à `400-VALIDATION`, une commande requise en échec conserve le statut `FAIL` même si les preuves runtime exécutées ensuite passent.
## Baseline opérateur
L'opérateur indique que la matérialisation de `beta.1` a été réalisée correctement avant application de `beta.1.fix.1`. Le rendu terminal du commit/push n'est plus disponible à la suite de la fermeture de la fenêtre GNOME Terminal après activation de `set -euo pipefail`.
L'état observé au démarrage de la gate confirme néanmoins :
```text
VERSION 0.0.2-beta.1.fix.1
Cargo workspace version 0.0.2-beta.1.fix.1
history/0.0.2/beta.1.md PARTIAL
```
## Contrôles documentaires et formatage
Les audits suivants passent :
```text
Markdown header audit clean (89 file(s))
Markdown link audit clean (72 local link(s), 89 file(s))
Markdown table audit clean (4 table(s), 89 file(s))
cargo fmt --all PASS
cargo fmt --all -- --check PASS
```
Le premier `git diff --check` de la gate signale toutefois :
```text
docs/validation/001-VALIDATION_MATRIX.md:782: new blank line at EOF.
```
## Audits Rust
Après formatage, les audits Rust passent :
```text
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages passent :
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test --workspace --all-targets --all-features PASS
```
Résultats observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les tests d'intégration Git réels restent propres.
## Baseline Web
Les commandes suivantes passent :
```text
npm ci
npm run typecheck
npm run build
```
Vite transforme `63` modules et produit le manifest attendu. Les warnings Sass issus de Bootstrap et l'avertissement npm sur le script d'installation de `@parcel/watcher@2.6.0` restent non bloquants.
## Échec de l'audit de stabilisation
Le bloc `PYGATE` échoue avec :
```text
SyntaxError: unterminated string literal
```
La cause est la chaîne Python suivante, délimitée par des apostrophes tout en contenant `n'est` :
```text
assert 'Aucun prompt `0.0.3` n'est créé' in study
```
Cet échec appartient à la gate livrée, pas au produit.
## Smokes HTTP
Malgré l'échec précédent, les trois smokes HTTP sont exécutés et passent :
```text
knowledge-pgr-server smoke PASS
knowledge-howtos-server smoke PASS
knowledge-snippets-server smoke PASS
Knowledge 0.0.2-beta.1.fix.1 HTTP smoke: clean
```
Ils prouvent `/healthz`, `/`, la présence des assets du manifest dans le HTML et leur service HTTP effectif.
## Hygiène texte et EOF
Le script d'hygiène texte affiche :
```text
Knowledge beta.1.fix.1 text hygiene: clean (89 file(s))
```
mais son contrat est insuffisant : il vérifie seulement qu'un fichier se termine par `\n`, pas qu'il se termine par exactement un seul `\n`. Il laisse donc passer un suffixe `\n\n`.
Après staging, `git diff --cached --check` échoue de nouveau sur la même ligne vide supplémentaire à EOF dans `001-VALIDATION_MATRIX.md`.
## Frontières
Les contrôles suivants passent :
```text
Cargo.lock présent localement PASS
Cargo.lock ignoré PASS
Cargo.lock non suivi PASS
aucun diff sous crates/ ou Web/ PASS
aucune crate forge Gitea PASS
aucun store PostgreSQL PASS
```
Le `git status --short` final montre uniquement les fichiers attendus de la tranche après correction de la faute de frappe opérateur `it status --short`.
## Décision
`0.0.2-beta.1.fix.1` est `FAIL`.
Les preuves runtime manquantes de `beta.1` sont désormais acquises, mais la stabilisation beta ne peut pas être fermée tant que la gate elle-même n'est pas propre.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-beta.1.fix.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-beta.1.fix.2
```
`beta.1.fix.2` doit rester documentaire/gate-only : corriger le quoting Python, normaliser les EOF à exactement un LF, rendre cette propriété vérifiable et ne pas réintroduire `set -euo pipefail` dans une gate destinée au shell interactif.

View File

@@ -0,0 +1,132 @@
<!-- file: history/0.0.2/beta.1.fix.2.md -->
<!-- version: 1 -->
# History 0.0.2-beta.1.fix.2 — fermeture propre de la stabilisation beta
## Statut
PASS
La gate opérateur de `0.0.2-beta.1.fix.2` exécutée le `2026-09-29` passe intégralement. Le fix corrige uniquement les défauts de validation de `beta.1.fix.1` : quoting Python, fin de fichier à exactement un LF et discipline du shell interactif.
Aucun code produit, contrat métier, package Cargo/npm ou comportement runtime n'est modifié.
## Baseline opérateur
`beta.1.fix.1` a été historisé, committé et poussé avant application de ce fix :
```text
commit 4b51098
message 0.0.2-beta.1.fix.1
branch master
push origin/master PASS
status clean après push
```
La gate démarre avec :
```text
VERSION 0.0.2-beta.1.fix.2
Cargo workspace version 0.0.2-beta.1.fix.2
history/0.0.2/beta.1.fix.1.md FAIL
```
## Diff et documentation
Les contrôles suivants passent :
```text
git diff --check PASS
Markdown header audit clean (91 file(s))
Markdown link audit clean (72 local link(s), 91 file(s))
Markdown table audit clean (4 table(s), 91 file(s))
```
## Audit de stabilisation corrigé
Le bloc `PYGATE` s'exécute sans erreur et confirme :
```text
workspace Cargo 9 membres
aucun prompt 0.0.3 PASS
aucun history beta.1.fix.2 anticipé PASS
preuves runtime beta.1.fix.1 présentes PASS
règle unique LF locale PASS
règle shell interactif locale PASS
Knowledge 0.0.2-beta.1.fix.2 stabilization audit: clean
```
Le quoting Python contenant `n'est` est donc corrigé.
## Hygiène texte et EOF
L'audit renforcé vérifie pour les `91` fichiers texte gouvernés :
```text
CR absent PASS
au moins un LF terminal PASS
pas de suffixe LF LF PASS
aucun espace/tab terminal par ligne PASS
```
Résultat :
```text
Knowledge beta.1.fix.2 text hygiene: clean (91 file(s))
```
La propriété « exactement un unique LF » est désormais effectivement prouvée.
## Frontières
Les contrôles suivants passent :
```text
Cargo.lock présent localement PASS
Cargo.lock ignoré PASS
Cargo.lock non suivi PASS
aucun diff sous crates/ ou Web/ PASS
aucune crate forge Gitea PASS
aucun store PostgreSQL PASS
```
Après staging :
```text
git diff --cached --check PASS
```
Le statut final contient exactement les fichiers documentaires/gate prévus par `beta.1.fix.2`.
## Preuves cumulatives de stabilisation
`beta.1.fix.1` conserve les preuves runtime acquises sur le même arbre produit :
```text
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features PASS
cargo test --workspace --all-targets --all-features PASS
npm ci / typecheck / build PASS
knowledge-pgr-server smoke PASS
knowledge-howtos-server smoke PASS
knowledge-snippets-server smoke PASS
```
`beta.1.fix.2` ne les rejoue pas parce qu'il ne modifie aucun fichier produit ; il ferme uniquement les défauts de la gate.
## Décision
`0.0.2-beta.1.fix.2` est `PASS`.
La stabilisation beta est fermée. La transition autorisée est uniquement :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-beta.1.fix.2"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-rc.1
```
`rc.1` doit geler le scope et rejouer la gate complète de release candidate sans nouvelle fonctionnalité.

173
history/0.0.2/beta.1.md Normal file
View File

@@ -0,0 +1,173 @@
<!-- file: history/0.0.2/beta.1.md -->
<!-- version: 1 -->
# History 0.0.2-beta.1 — réconciliation et revue d'horizon
## Statut
PARTIAL
La gate opérateur de `0.0.2-beta.1` exécutée le `2026-09-29` acquiert toutes les preuves documentaires, Rust, Git et Web demandées par le delta, mais elle ne contient pas les trois smokes HTTP pourtant requis par le plan `0.0.2` pendant la stabilisation.
L'absence de cette preuve provient d'une omission de la gate `beta.1` préparée par l'assistant ; aucune commande opérateur transmise n'a échoué. Le statut exact est donc `PARTIAL`, et non `PASS` ni `FAIL`.
## Baseline opérateur
`alpha.5` `PASS` a été historisé, committé et poussé avant application de `beta.1` :
```text
history/0.0.2/alpha.5.md PASS
commit da54453
message 0.0.2-alpha.5
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `1fe39df` à `da54453`.
## Version et audits documentaires
Les contrôles passent :
```text
VERSION 0.0.2-beta.1
Cargo workspace version 0.0.2-beta.1
git diff --check PASS
Markdown header audit clean (87 file(s))
Markdown link audit clean (72 local link(s), 87 file(s))
Markdown table audit clean (4 table(s), 87 file(s))
```
## Formatage et audits Rust
L'ordre local est respecté :
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en `0.0.2-beta.1`.
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test --workspace --all-targets --all-features PASS
```
Tests observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les deux tests d'intégration Git réels continuent de prouver le refresh, la résolution, les snapshots, la référence invalide et le snapshot sale.
## Baseline Web et manifest Vite
Les commandes suivantes passent :
```text
npm ci
npm run typecheck
npm run build
```
npm observe `@parcel/watcher@2.6.0` comme package avec script d'installation non couvert par `allowScripts`. Aucune approbation automatique n'est effectuée.
Les warnings de dépréciation Sass issus de Bootstrap restent non bloquants ; Vite termine avec succès et transforme `63` modules.
Le manifest généré est parsable et contient l'entrée stable `src/main.ts`.
## Réconciliation et revue d'horizon
L'audit passe :
```text
Knowledge 0.0.2-beta.1 reconciliation/horizon audit: clean
Knowledge beta.1 text hygiene: clean (87 file(s))
```
Il confirme notamment :
- neuf packages Cargo exactement ;
- `knowledge-git-lib` comme backend Git générique et `knowledge-app-lib` comme owner du cas d'usage partagé ;
- `DEFER` conservés pour PostgreSQL, forge, auth/admin, écriture Git, scheduler, submodules/LFS, rétention des snapshots et partage multi-processus ;
- `CHANGELOG.md` en état candidate sans catalogue des fixes internes ;
- aucune release postérieure à `0.0.2` engagée ni numérotée ;
- aucun prompt `0.0.3` anticipé ;
- aucun diff sous `crates/` ou `Web/` ;
- `Cargo.lock` présent localement, ignoré et non suivi.
Après staging, `git diff --cached --check` passe.
## Preuve manquante
Le plan `docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md` exige pendant la stabilisation la non-régression Web complète, y compris les smokes HTTP des trois launchers.
Le delta et la matrice `beta.1` ont omis ces commandes. Le transcript ne peut donc pas établir :
```text
knowledge-pgr-server smoke HTTP
knowledge-howtos-server smoke HTTP
knowledge-snippets-server smoke HTTP
```
Les preuves de `0.0.1` ne remplacent pas une preuve de stabilisation `0.0.2`.
## Staging observé
Avant ajout du présent history :
```text
M CHANGELOG.md
M Cargo.toml
M README.md
M ROADMAP.md
M VERSION
A deltas/0.0.2/beta.1.md
M docs/000-README.md
M docs/architecture/001-PROJECT_PROFILE.md
M docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
A docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.2-beta.1` est `PARTIAL`.
La réconciliation et la revue d'horizon sont acquises, mais la phase beta ne peut pas être fermée sans les smokes runtime requis.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2-beta.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.2-beta.1.fix.1
```
`beta.1.fix.1` ne doit introduire aucune fonctionnalité ; il corrige le contrat de validation et rejoue la gate de stabilisation complète avec les trois smokes HTTP.

156
history/0.0.2/rc.1.md Normal file
View File

@@ -0,0 +1,156 @@
<!-- file: history/0.0.2/rc.1.md -->
<!-- version: 1 -->
# History 0.0.2-rc.1 — candidate stable
## Statut
PASS
La gate opérateur de `0.0.2-rc.1` exécutée le `2026-09-29` est propre.
## Baseline opérateur
`beta.1.fix.2` `PASS` a été historisé, committé et poussé avant application de `rc.1` :
```text
history/0.0.2/beta.1.fix.2.md = PASS
commit 6de544f
message 0.0.2-beta.1.fix.2
branch master
push origin/master PASS
status clean après push
```
Le push observé a avancé `origin/master` de `4b51098` à `6de544f`.
## Cohérence documentaire et format
Les preuves suivantes passent :
```text
git diff --check PASS
Markdown header audit clean (93 file(s))
Markdown link audit clean (72 local link(s), 93 file(s))
Markdown table audit clean (4 table(s), 93 file(s))
cargo fmt --all PASS
cargo fmt --all -- --check PASS
```
## Audits Rust
Les audits passent après formatage :
```text
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en version `0.0.2-rc.1`.
```text
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features PASS
cargo test -p knowledge-git-lib --test git_read_path 2 PASS
cargo test --workspace --all-targets --all-features PASS
```
Les tests workspace confirment notamment :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test, compilation PASS
```
## Web et manifest Vite
La baseline Web passe :
```text
npm ci PASS
npm run typecheck PASS
npm run build PASS
```
Les avertissements Sass/Bootstrap de dépréciation et le warning npm relatif au script d'installation de `@parcel/watcher` restent non bloquants ; les commandes terminent avec succès.
Le manifest Vite est présent et valide :
```text
Knowledge 0.0.2-rc.1 Vite manifest audit: clean
```
## Audit release candidate
L'audit de cohérence passe :
```text
Knowledge 0.0.2-rc.1 release-candidate audit: clean
```
Il confirme :
- neuf packages Cargo exactement ;
- les treize histories antérieurs avec leurs statuts réels ;
- `beta.1.fix.2 = PASS` ;
- `rc.1` comme seul delta `0.0.2` sans history pendant sa gate ;
- changelog encore marqué `0.0.2 (candidate)` ;
- roadmap avec `0.0.2` encore ouverte et aucune `0.0.3` réservée ;
- absence des crates forge/PostgreSQL différées.
L'hygiène texte passe avec exactement un LF terminal sur les fichiers gouvernés.
## Smokes HTTP
Les trois launchers passent explicitement :
```text
knowledge-pgr-server smoke PASS
knowledge-howtos-server smoke PASS
knowledge-snippets-server smoke PASS
Knowledge 0.0.2-rc.1 HTTP smoke: clean
```
Les smokes confirment `/healthz`, `/`, les références du manifest Vite dans le HTML et le service HTTP effectif des assets JS/CSS hashés.
## Hygiène Git finale
`Cargo.lock` reste présent localement, ignoré et non suivi.
Aucun changement RC n'est introduit sous les surfaces gelées vérifiées. Le staging final avant ajout du présent history est exactement :
```text
M Cargo.toml
M README.md
M VERSION
A deltas/0.0.2/rc.1.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
`git diff --cached --check` passe.
## Décision
`0.0.2-rc.1` est `PASS`.
La candidate stable est validée. La seule transition autorisée est :
```text
0.0.2-rel.1
```
`rel.1` doit rester une promotion stable mécanique et pré-tag. Aucun nouveau scope fonctionnel, aucune `0.0.3` et aucun rapport post-tag ne doivent être anticipés dans cette tranche.

210
history/0.0.2/rel.1.md Normal file
View File

@@ -0,0 +1,210 @@
<!-- file: history/0.0.2/rel.1.md -->
<!-- version: 1 -->
# History 0.0.2 rel.1 — promotion stable pré-tag
## Statut
PASS
La gate opérateur de `0.0.2-rel.1` exécutée le `2026-09-29` est propre.
## Baseline opérateur
`rc.1` `PASS` a été historisé, committé et poussé avant application de `rel.1` :
```text
history/0.0.2/rc.1.md = PASS
commit 3bb1ffe
message 0.0.2-rc.1
branch master
push origin/master PASS
status clean après push
```
Le premier `git push origin master` a été interrompu pendant la demande d'identifiant puis relancé ; le second push a réussi et a avancé `origin/master` de `6de544f` à `3bb1ffe`.
## Promotion stable
La version produit est :
```text
VERSION 0.0.2
Cargo workspace version 0.0.2
```
La tranche ne modifie aucun fichier sous :
```text
crates/
Web/
docs/architecture/
docs/studies/
docs/rules/
prompts/
```
La présentation stable est cohérente :
```text
ROADMAP 0.0.2 terminé ; aucune release postérieure engagée
CHANGELOG section ## 0.0.2 sans marqueur candidate
README 0.0.2 présentée comme stable du chemin Git générique de lecture
plan 002 plan historique de la stable 0.0.2
index docs plan 002 référencé comme plan historique de la stable Git générique
prompt 0.0.3 absent
```
## Markdown et format
Les preuves suivantes passent :
```text
git diff --check PASS
Markdown header audit clean (95 file(s))
Markdown link audit clean (72 local link(s), 95 file(s))
Markdown table audit clean (4 table(s), 95 file(s))
cargo fmt --all PASS
cargo fmt --all -- --check PASS
```
## Audits Rust
Les audits passent après formatage :
```text
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en version stable `0.0.2`.
```text
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features PASS
cargo test -p knowledge-git-lib --test git_read_path 2 PASS
cargo test --workspace --all-targets --all-features PASS
```
Les tests workspace confirment notamment :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test, compilation PASS
```
## Web et manifest Vite
La baseline Web passe :
```text
npm ci PASS
npm run typecheck PASS
npm run build PASS
```
Les avertissements Sass/Bootstrap de dépréciation et le warning npm relatif au script d'installation de `@parcel/watcher` restent non bloquants ; les commandes terminent avec succès.
Le manifest Vite est présent et valide :
```text
Knowledge 0.0.2-rel.1 Vite manifest audit: clean
```
## Audit stable
L'audit de cohérence passe :
```text
Knowledge 0.0.2-rel.1 stable audit: clean
```
Il confirme notamment :
- neuf packages Cargo exactement ;
- les quatorze histories antérieurs avec leurs statuts réels ;
- `rc.1 = PASS` ;
- `rel.1` comme seul delta `0.0.2` sans history pendant sa gate ;
- `CHANGELOG.md` stable sans marqueur `candidate` ;
- roadmap `0.0.2` terminée ;
- aucune `0.0.3` réservée et aucun prompt `0.0.3` créé ;
- absence des crates forge/PostgreSQL différées.
L'hygiène texte passe avec exactement un LF terminal sur les `95` fichiers gouvernés :
```text
Knowledge 0.0.2-rel.1 text hygiene: clean (95 file(s))
```
## Smokes HTTP
Les trois launchers passent explicitement :
```text
knowledge-pgr-server smoke PASS
knowledge-howtos-server smoke PASS
knowledge-snippets-server smoke PASS
Knowledge 0.0.2-rel.1 HTTP smoke: clean
```
Les smokes confirment `/healthz`, `/`, les références du manifest Vite dans le HTML et le service HTTP effectif des assets JS/CSS hashés.
## Hygiène Git finale
`Cargo.lock` reste présent localement, ignoré et non suivi.
Les surfaces produit et normatives gelées ne sont pas modifiées par `rel.1`.
Le staging final observé avant ajout du présent history est exactement :
```text
M CHANGELOG.md
M Cargo.toml
M README.md
M ROADMAP.md
M VERSION
A deltas/0.0.2/rel.1.md
M docs/000-README.md
M docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
`git diff --cached --check` passe.
## Décision
`0.0.2-rel.1` est `PASS`.
La release stable `0.0.2` peut maintenant être matérialisée.
L'ordre obligatoire est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.2"
-> push origin master
-> vérifier workspace propre
-> créer le tag annoté v0.0.2 sur ce commit
-> pousser le tag
-> vérifier tag local et distant et le commit déréférencé
-> télécharger l'archive réellement servie par Gitea depuis v0.0.2
-> produire sasedev-knowledge-0.0.2-publish.001-report.md
```
Le présent history ne déclare pas le tag, l'archive du tag ou le rapport post-tag comme déjà existants.
Aucune prochaine release n'est engagée. Un futur prompt de démarrage ne doit être créé qu'après décision explicite de sa mission et doit s'appuyer sur la stable publiée/vérifiée `0.0.2` et les artefacts locaux Knowledge, sans consultation PGR automatique.

View File

@@ -0,0 +1,134 @@
<!-- file: history/0.0.3/alpha.1.fix.1.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.1.fix.1 — cycle documentaire et horizon
## Statut
PARTIAL
La gate opérateur de `0.0.3-alpha.1.fix.1` exécutée le `2026-09-29` acquiert toutes les preuves transmises jusqu'aux tests workspace, mais le transcript reçu s'arrête avant le bloc final explicitement requis par le delta.
Aucune commande observée n'a échoué. Le statut exact est donc `PARTIAL`, et non `PASS` ni `FAIL`.
## Baseline opérateur
`alpha.1` `PARTIAL` a été historisé, committé et poussé avant application de `alpha.1.fix.1` :
```text
history/0.0.3/alpha.1.md PARTIAL
commit a74a198
message 0.0.3-alpha.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `183d354` à `a74a198`.
## Version et audits documentaires
Les contrôles observés passent :
```text
VERSION 0.0.3-alpha.1.fix.1
Cargo workspace version 0.0.3-alpha.1.fix.1
git diff --check PASS
Markdown header audit clean (102 file(s))
Markdown link audit clean (78 local link(s), 102 file(s))
Markdown table audit clean (4 table(s), 102 file(s))
```
## Formatage et audits Rust
L'ordre local est respecté :
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en `0.0.3-alpha.1.fix.1`.
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test -p knowledge-git-lib --test git_read_path PASS (2)
cargo test --workspace --all-targets --all-features PASS
```
Tests workspace observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les deux tests d'intégration Git réels continuent de prouver les chemins de lecture déjà acquis : refresh/résolution/snapshot, rejet d'une référence invalide et rejet d'un snapshot existant sale.
## Correction documentaire acquise dans le contenu
Le delta appliqué porte bien la correction décidée après `alpha.1` :
- l'entrée candidate `0.0.3` a été retirée du changelog ;
- le changelog est désormais défini comme artefact de stabilisation plutôt que de démarrage ;
- le prompt de `N+1` est normalement préparé pendant les dernières phases de `N` après revue d'horizon ;
- `prompts/003-V0_0_3_START_PROMPT.md` reste le rattrapage historique nécessaire pour `0.0.3` ;
- la roadmap distingue la release engagée d'un horizon prévisionnel révisable ;
- le plan `0.0.3` prévoit une revue d'horizon en `beta.1`.
La revue opérateur postérieure précise que l'horizon actuel reste volontairement incomplet : les futures revues devront aussi intégrer explicitement les phases de templates/frontends publics et admin des trois sites PGR, Snippets et Howtos, leur intégration Actix Web/Maud, ainsi que Git et PostgreSQL. Cette précision est un besoin d'horizon futur, pas un échec de la correction courante.
## Preuves manquantes
Le delta `alpha.1.fix.1` exigeait encore après les tests workspace :
```text
audit Python du cycle documentaire/horizon
hygiène texte des fichiers gouvernés
absence de diff sous crates/ et Web/
absence du history alpha.1.fix.1 pendant la gate
politique Cargo.lock
présence du package-lock Web
git add -A
git diff --cached --check
git status --short
```
Ces résultats ne figurent pas dans le transcript opérateur reçu. Ils ne sont donc ni inventés ni assimilés à un PASS.
## Décision
`0.0.3-alpha.1.fix.1` est `PARTIAL`.
La correction documentaire et la non-régression Rust observée sont acquises, mais la tranche ne peut pas être clôturée en `PASS` sans les preuves finales explicitement requises.
La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.3-alpha.1.fix.1"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.3-alpha.1.fix.2
```
`alpha.1.fix.2` reste une correction de validation bornée : aucune fonctionnalité Git, Web ou PostgreSQL ne doit y être ajoutée. Un `PASS` de ce fix autorisera `alpha.2`.

View File

@@ -0,0 +1,143 @@
<!-- file: history/0.0.3/alpha.1.fix.2.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.1.fix.2 — complétion de la preuve opérateur
## Statut
PASS
La gate opérateur de `0.0.3-alpha.1.fix.2` exécutée le `2026-09-29` est complète et propre. Elle ferme uniquement la preuve restée incomplète après `alpha.1.fix.1` et autorise l'ouverture fonctionnelle de `alpha.2`.
## Baseline opérateur
`alpha.1.fix.1` `PARTIAL` a été historisé, committé et poussé avant application de `alpha.1.fix.2` :
```text
history/0.0.3/alpha.1.fix.1.md PARTIAL
commit dd63e85
message 0.0.3-alpha.1.fix.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `a74a198` à `dd63e85`.
## Version et documentation
Les contrôles de version et Markdown passent :
```text
VERSION 0.0.3-alpha.1.fix.2
Cargo workspace version 0.0.3-alpha.1.fix.2
git diff --check PASS
Markdown header audit clean (104 file(s))
Markdown link audit clean (78 local link(s), 104 file(s))
Markdown table audit clean (4 table(s), 104 file(s))
```
## Formatage et audits Rust
La séquence locale est respectée et propre :
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (32 file(s))
Rust item-spacing audit clean (32 file(s))
Rust module-order audit clean (32 file(s))
Rust trait-import-block audit clean (32 file(s))
Rust re-export-block audit clean (32 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en `0.0.3-alpha.1.fix.2`.
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test -p knowledge-git-lib --test git_read_path PASS (2)
cargo test --workspace --all-targets --all-features PASS
```
Tests workspace observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
## Complétion de la preuve documentaire
L'audit spécifique de fin de gate passe :
```text
Knowledge 0.0.3-alpha.1.fix.2 completion audit: clean
```
Il confirme notamment :
- `history/0.0.3/alpha.1.fix.1.md = PARTIAL` conservé sans réécriture ;
- aucune section `0.0.3` prématurée dans `CHANGELOG.md` ;
- `ROADMAP.md` contient `0.0.3` engagée et `0.0.4` à `0.0.7` comme horizon prévisionnel ;
- le changelog est défini comme artefact de stabilisation et non de démarrage ;
- le prompt `N+1` est normalement préparé pendant les dernières phases de `N` ;
- `prompts/003-V0_0_3_START_PROMPT.md` reste le rattrapage historique attendu ;
- le plan autorise `0.0.3-alpha.2` seulement après le présent PASS ;
- aucune crate Gitea/PostgreSQL et aucun répertoire `migrations/` ne sont introduits.
La revue d'horizon plus large des trois sites PGR, Snippets et Howtos reste volontairement prévue plus tard dans la release ; elle n'est pas absorbée par ce fix de validation.
## Hygiène texte, scope et lockfiles
L'hygiène texte passe sur les `104` fichiers gouvernés :
```text
Knowledge 0.0.3-alpha.1.fix.2 text hygiene: clean (104 file(s))
```
Les contrôles finaux passent également :
- aucun diff sous `crates/` ni `Web/` ;
- aucun history `alpha.1.fix.2` présent pendant la gate ;
- `Cargo.lock` présent localement, ignoré et non suivi ;
- `Web/knowledge-template-lab/package-lock.json` présent ;
- staging final sans erreur via `git diff --cached --check`.
Le staging observé est exactement :
```text
M Cargo.toml
M VERSION
A deltas/0.0.3/alpha.1.fix.2.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
## Décision
`0.0.3-alpha.1.fix.2` est `PASS`.
La séquence de cadrage/correction est fermée. La transition autorisée est :
```text
appliquer ce history
-> git add / diff --cached --check / status
-> commit exact "0.0.3-alpha.1.fix.2"
-> push origin master
-> vérifier le workspace propre
-> appliquer seulement ensuite 0.0.3-alpha.2
```
`alpha.2` peut maintenant matérialiser le contrat d'écriture Git provider-neutral dans `knowledge-git-api`, sans implémenter encore le worktree d'écriture ni le push distant réel.

84
history/0.0.3/alpha.1.md Normal file
View File

@@ -0,0 +1,84 @@
<!-- file: history/0.0.3/alpha.1.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.1 — cadrage de l'écriture Git contrôlée
## Statut
`PARTIAL`
## Baseline observée
La tranche a été appliquée sur la stable autoritaire `v0.0.2` puis a porté `VERSION` et le workspace Cargo à :
```text
0.0.3-alpha.1
```
## Preuves opérateur reçues
Les preuves transmises le `2026-09-29` montrent :
- contrôle `VERSION` / `workspace.package.version` conforme ;
- `git diff --check` propre ;
- audits Markdown headers/liens/tables propres ;
- `cargo fmt --all` puis `cargo fmt --all -- --check` PASS ;
- audits Rust structurels propres sur les neuf crates / trente-deux fichiers concernés ;
- `cargo metadata --format-version 1 --no-deps` exécuté ;
- `cargo check --workspace` PASS ;
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` PASS ;
- `cargo test -p knowledge-git-lib --test git_read_path` : `2 passed` ;
- `cargo test --workspace --all-targets --all-features` : tous les tests observés PASS.
Les comptes observés dans la gate workspace restent cohérents avec la stable :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 1 PASS
knowledge-git-lib 8 unit + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun, compilation PASS
```
## Limites de la gate reçue
Le retour fourni s'arrête après `cargo test --workspace --all-targets --all-features`.
Les commandes de fin annoncées dans le delta `alpha.1` ne sont donc pas présentées comme exécutées :
- audit Python spécifique de cadrage `alpha.1` ;
- audit d'hygiène texte ;
- vérifications finales `crates/` / `Web/`, lockfiles et absence de history prématuré ;
- `git add -A`, `git diff --cached --check` et `git status --short`.
Cette absence suffit à empêcher un statut `PASS` intégral de la gate telle qu'elle était spécifiée.
## Revue opérateur du cycle documentaire
La revue manuelle a en outre identifié trois corrections de gouvernance/documentation à matérialiser avant de poursuivre le développement fonctionnel :
1. `CHANGELOG.md` ne doit pas être rédigé au démarrage d'une release ; il doit synthétiser les changements réellement acquis vers les phases de stabilisation/fin de release ;
2. le prompt de démarrage de la release suivante doit normalement être préparé pendant les dernières phases de la release en cours, après revue d'horizon ; l'ajout de `prompts/003-V0_0_3_START_PROMPT.md` pendant `0.0.3-alpha.1` est accepté uniquement comme rattrapage parce que `0.0.2` avait été fermée sans ce handoff ;
3. `ROADMAP.md` doit montrer à la fois la release en cours et un horizon borné des releases suivantes, en distinguant clairement l'engagement courant des versions d'horizon encore révisables.
Ces défauts ne remettent pas en cause la mission Git choisie pour `0.0.3`, mais rendent le cadrage documentaire incomplet.
## Décision
`0.0.3-alpha.1` est conservée avec le statut `PARTIAL`.
La correction reste bornée au workflow documentaire et utilise :
```text
0.0.3-alpha.1.fix.1
```
Le fix doit :
- retirer la section candidate `0.0.3` prématurée du changelog ;
- transformer la roadmap en roadmap courante + horizon prévisionnel ;
- rendre explicite chez les owners locaux le moment de rédaction du changelog et du prompt de release suivante ;
- mettre à jour le plan `0.0.3` pour refléter cette correction sans modifier la mission Git ;
- ne modifier aucun code sous `crates/` ni aucun fichier sous `Web/`.

142
history/0.0.3/alpha.2.md Normal file
View File

@@ -0,0 +1,142 @@
<!-- file: history/0.0.3/alpha.2.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.2 — contrat d'écriture Git provider-neutral
## Statut
PASS
La gate opérateur de `0.0.3-alpha.2` exécutée le `2026-09-29` est complète et propre. Elle valide la frontière publique d'écriture Git provider-neutral et autorise `0.0.3-alpha.3` pour le worktree d'écriture isolé et la création réelle du commit candidat.
## Baseline opérateur
`alpha.1.fix.2` `PASS` a été historisé, committé et poussé avant application de `alpha.2` :
```text
history/0.0.3/alpha.1.fix.2.md PASS
commit 3004e4b
message 0.0.3-alpha.1.fix.2
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `dd63e85` à `3004e4b`.
## Version et documentation
Les contrôles de version et Markdown passent :
```text
VERSION 0.0.3-alpha.2
Cargo workspace version 0.0.3-alpha.2
git diff --check PASS
Markdown header audit clean (106 file(s))
Markdown link audit clean (78 local link(s), 106 file(s))
Markdown table audit clean (4 table(s), 106 file(s))
```
## Formatage et audits Rust
La séquence locale imposée a été respectée :
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (33 file(s))
Rust item-spacing audit clean (33 file(s))
Rust module-order audit clean (33 file(s))
Rust trait-import-block audit clean (33 file(s))
Rust re-export-block audit clean (33 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
Les neuf packages compilent en `0.0.3-alpha.2` et Clippy ne produit aucun warning avec `-D warnings`.
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test -p knowledge-git-api --all-targets --all-features PASS (7)
cargo test --workspace --all-targets --all-features PASS
```
Tests workspace observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 7 PASS
knowledge-git-lib 8 unit PASS + 2 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les sept tests `knowledge-git-api` valident notamment :
- la séparation du contrat d'écriture du contrat de lecture ;
- l'absence de dépendance à une forge ;
- la validation des chemins de mutation et la protection de `.git` ;
- l'identité de commit explicite ;
- les branches bornées à `refs/heads/...` ;
- les états distants absent/révision ;
- le rejet des requêtes sans mutation, messages invalides et chemins dupliqués.
## Audit spécifique d'écriture
L'audit de tranche passe :
```text
Knowledge 0.0.3-alpha.2 write-contract audit: clean
```
Il confirme notamment :
- la présence et le réexport des dix types/traits d'écriture attendus ;
- `GitRepository` de lecture inchangé et sans méthodes `create_candidate` / `push_candidate` ;
- la validation `refs/heads/...`, `ProtectedGitPath` et `DuplicateMutationPath` ;
- l'absence de dépendances Actix, Tokio, forge, PostgreSQL ou ORM dans `knowledge-git-api` ;
- `CHANGELOG.md` toujours sans section prématurée `0.0.3` ;
- absence de history `alpha.2` avant staging ;
- absence de crates Gitea/PostgreSQL et de migrations.
## Hygiène et scope
```text
Knowledge 0.0.3-alpha.2 text hygiene: clean (106 file(s))
```
La gate confirme également :
- aucun diff sous `crates/knowledge-git-lib`, `crates/knowledge-app-lib` ou `Web/` ;
- `Cargo.lock` local présent, ignoré et non suivi ;
- `Web/knowledge-template-lab/package-lock.json` présent ;
- `git diff --cached --check` propre.
Le staging final contient exactement les changements attendus de la tranche :
```text
M Cargo.toml
M README.md
M VERSION
M crates/knowledge-git-api/src/lib.rs
A crates/knowledge-git-api/src/write.rs
M crates/knowledge-git-api/unit_tests/lib.rs
A deltas/0.0.3/alpha.2.md
M docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.3-alpha.2` est `PASS`.
La frontière provider-neutral est acquise. La tranche suivante peut implémenter dans `knowledge-git-lib` le worktree d'écriture isolé, les mutations confinées, l'identité explicite, la neutralisation des hooks/configurations pertinentes et la création d'une révision candidate vérifiée. Le push distant gardé reste réservé à `alpha.4`.

View File

@@ -0,0 +1,123 @@
<!-- file: history/0.0.3/alpha.3.fix.1.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.3.fix.1 — correction de l'assertion du test candidat
## Statut
PASS
La gate opérateur de `0.0.3-alpha.3.fix.1` exécutée le `2026-09-29` est complète et propre. Le défaut de compilation `E0369` de `alpha.3` est corrigé sans modification du code produit : l'assertion du test d'intégration vérifie désormais le cas `Ok("first\n")` sans exiger `PartialEq` sur `std::io::Error`.
## Baseline opérateur
`alpha.3` `FAIL` a été historisé, committé et poussé avant application du fix :
```text
history/0.0.3/alpha.3.md FAIL
commit c3a8256
message 0.0.3-alpha.3
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `88a63b0` à `c3a8256`.
## Version et documentation
```text
VERSION 0.0.3-alpha.3.fix.1
Cargo workspace version 0.0.3-alpha.3.fix.1
git diff --check PASS
Markdown header audit clean (110 file(s))
Markdown link audit clean (78 local link(s), 110 file(s))
Markdown table audit clean (4 table(s), 110 file(s))
```
## Formatage et audits Rust
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (35 file(s))
Rust item-spacing audit clean (35 file(s))
Rust module-order audit clean (35 file(s))
Rust trait-import-block audit clean (35 file(s))
Rust re-export-block audit clean (35 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
La correction compile et l'ensemble des gates Rust passent :
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test -p knowledge-git-lib --test git_read_path PASS (2)
cargo test -p knowledge-git-lib --test git_write_candidate PASS (2)
cargo test --workspace --all-targets --all-features PASS
```
Tests workspace observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 7 PASS
knowledge-git-lib 8 unit + 4 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les deux tests d'écriture réels prouvent maintenant :
- create/update/delete exacts dans le candidat ;
- identité auteur/committer explicite ;
- parent candidat égal à la base demandée ;
- référence candidate interne conservée ;
- snapshot de lecture laissé inchangé ;
- nettoyage du worktree temporaire ;
- refus d'une traversée par symlink hors du worktree.
## Contrôles spécifiques
Le contrôle spécifique a été exécuté avec succès puis rejoué commande par commande après un problème de copier/coller terminal. Les deux passages produisent les mêmes résultats :
```text
Knowledge 0.0.3-alpha.3.fix.1 compile-fix audit: clean
Knowledge 0.0.3-alpha.3.fix.1 text hygiene: clean (110 file(s))
```
La gate confirme également :
- `history/0.0.3/alpha.3.md = FAIL` conservé ;
- ancienne assertion `assert_eq!` absente ;
- nouvelle assertion `matches!(snapshot_content.as_deref(), Ok("first\n"))` présente ;
- aucun diff sous le code produit `knowledge-git-lib/src`, ses unit tests, `knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` ou `ROADMAP.md` ;
- crates Gitea/PostgreSQL et migrations toujours absentes ;
- `Cargo.lock` local présent, ignoré et non suivi ;
- package-lock Web présent ;
- `git diff --cached --check` propre.
Le staging final est exactement :
```text
M Cargo.toml
M VERSION
M crates/knowledge-git-lib/tests/git_write_candidate.rs
A deltas/0.0.3/alpha.3.fix.1.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
```
## Décision
`0.0.3-alpha.3.fix.1` est `PASS`.
La création locale du candidat est acquise. `0.0.3-alpha.4` peut implémenter l'observation distante et le push gardé avec comparaison-et-échange, conflit récupérable et tests sur remote bare local, sans introduire forge, PostgreSQL, rebase/merge automatique ni workflow applicatif de proposition.

149
history/0.0.3/alpha.3.md Normal file
View File

@@ -0,0 +1,149 @@
<!-- file: history/0.0.3/alpha.3.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.3 — worktree d'écriture et commit candidat
## Statut
FAIL
La gate opérateur de `0.0.3-alpha.3` exécutée le `2026-09-29` échoue sur la compilation du nouveau test d'intégration `git_write_candidate`. Le défaut est limité à une assertion de test qui tente de comparer directement deux `Result<String, std::io::Error>` alors que `std::io::Error` n'implémente pas `PartialEq`.
L'implémentation produit passe `cargo check --workspace`; les audits structurels et spécifiques de la tranche restent propres. Le statut demeure néanmoins `FAIL`, car Clippy `--all-targets` et les tests requis ne compilent pas.
## Baseline opérateur
`alpha.2` `PASS` a été historisé, committé et poussé avant application de `alpha.3` :
```text
history/0.0.3/alpha.2.md PASS
commit 88a63b0
message 0.0.3-alpha.2
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `3004e4b` à `88a63b0`.
## Version et documentation
Les contrôles initiaux passent :
```text
VERSION 0.0.3-alpha.3
Cargo workspace version 0.0.3-alpha.3
git diff --check PASS
Markdown header audit clean (108 file(s))
Markdown link audit clean (78 local link(s), 108 file(s))
Markdown table audit clean (4 table(s), 108 file(s))
```
## Formatage et audits Rust
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (35 file(s))
Rust item-spacing audit clean (35 file(s))
Rust module-order audit clean (35 file(s))
Rust trait-import-block audit clean (35 file(s))
Rust re-export-block audit clean (35 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
`cargo metadata` et `cargo check --workspace` passent. La première défaillance obligatoire survient pendant Clippy :
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets
--all-features -- -D warnings FAIL
```
Erreur observée :
```text
error[E0369]: binary operation `==` cannot be applied to type
`Result<String, std::io::Error>`
crates/knowledge-git-lib/tests/git_write_candidate.rs:253
assert_eq!(snapshot_content, Ok(String::from("first\n")));
std::io::Error does not implement PartialEq
```
Le chemin de lecture historique reste propre :
```text
cargo test -p knowledge-git-lib --test git_read_path PASS (2)
```
Le nouveau test ciblé et la gate workspace reproduisent le même défaut de compilation :
```text
cargo test -p knowledge-git-lib --test git_write_candidate FAIL E0369
cargo test --workspace --all-targets --all-features FAIL E0369
```
Aucun échec runtime du worktree/candidat n'est donc observé dans cette gate : `git_write_candidate` ne peut pas encore être exécuté jusqu'à ses assertions.
## Audits spécifiques après l'échec
Les contrôles statiques et de scope exécutés après l'échec passent :
```text
Knowledge 0.0.3-alpha.3 candidate-worktree audit: clean
Knowledge 0.0.3-alpha.3 text hygiene: clean (108 file(s))
```
Ils confirment notamment :
- le contrat `alpha.2` historisé `PASS` ;
- la présence du worktree candidat, des contrôles de symlink, de l'identité explicite, de `--no-verify`, `--no-gpg-sign`, `HEAD^1` et de la référence candidate interne ;
- la neutralisation des configurations Git globale/système et des hooks ;
- l'absence de `push_candidate` dans l'implémentation `alpha.3` ;
- l'absence de diff sous `knowledge-git-api`, `knowledge-app-lib`, `Web/`, `CHANGELOG.md` et `ROADMAP.md` ;
- `Cargo.lock` présent localement, ignoré et non suivi ;
- le package-lock Web présent.
Le staging final est cohérent avec la tranche :
```text
M Cargo.toml
M README.md
M VERSION
M crates/knowledge-git-lib/src/backend.rs
M crates/knowledge-git-lib/src/error.rs
M crates/knowledge-git-lib/src/lib.rs
A crates/knowledge-git-lib/src/write.rs
A crates/knowledge-git-lib/tests/git_write_candidate.rs
M crates/knowledge-git-lib/unit_tests/backend.rs
A deltas/0.0.3/alpha.3.md
M docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Diagnostic
Le défaut est borné au test :
```rust
let snapshot_content = std::fs::read_to_string(snapshot.path().join("content.txt"));
assert_eq!(snapshot_content, Ok(String::from("first\n")));
```
Cette comparaison exige `PartialEq` sur le type d'erreur du `Result`, ce que `std::io::Error` ne fournit pas. Le test doit vérifier le cas `Ok("first\n")` sans comparer la variante `Err`.
Aucun élément du retour opérateur ne justifie un REPLAN, une modification du contrat public ou un changement de l'implémentation de worktree.
## Décision
`0.0.3-alpha.3` est `FAIL`.
La correction autorisée est `0.0.3-alpha.3.fix.1`, bornée à l'assertion de test fautive et au suivi version/plan. Elle doit rejouer Clippy, le test `git_write_candidate` et la gate workspace avant d'autoriser `alpha.4`.

132
history/0.0.3/alpha.4.md Normal file
View File

@@ -0,0 +1,132 @@
<!-- file: history/0.0.3/alpha.4.md -->
<!-- version: 1 -->
# History 0.0.3-alpha.4 — push gardé et concurrence
## Statut
PASS
La gate opérateur de `0.0.3-alpha.4` exécutée le `2026-09-29` est complète et propre. Le chemin Git d'écriture provider-neutral sait désormais pousser un candidat vers une branche distante uniquement lorsque l'état distant correspond à l'attente explicite, avec conflit récupérable en cas de divergence concurrente et sans écrasement silencieux.
## Baseline opérateur
`alpha.3.fix.1` `PASS` a été historisé, committé et poussé avant application de `alpha.4` :
```text
history/0.0.3/alpha.3.fix.1.md PASS
commit aca00e6
message 0.0.3-alpha.3.fix.1
branch master
push origin/master PASS
workspace après push propre
```
Le push observé a avancé `origin/master` de `c3a8256` à `aca00e6`.
## Version et documentation
```text
VERSION 0.0.3-alpha.4
Cargo workspace version 0.0.3-alpha.4
git diff --check PASS
Markdown header audit clean (112 file(s))
Markdown link audit clean (78 local link(s), 112 file(s))
Markdown table audit clean (4 table(s), 112 file(s))
```
## Formatage et audits Rust
```text
cargo fmt --all PASS
cargo fmt --all -- --check PASS
Rust test-layout audit clean (9 crate(s))
Rust blank-line audit clean (36 file(s))
Rust item-spacing audit clean (36 file(s))
Rust module-order audit clean (36 file(s))
Rust trait-import-block audit clean (36 file(s))
Rust re-export-block audit clean (36 file(s))
Rust crate-root facade audit clean (6 crate root(s))
Rust export completeness audit 0 candidate(s) across 9 package(s)
```
## Cargo, Clippy et tests
```text
cargo metadata --format-version 1 --no-deps PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets --all-features
-- -D warnings PASS
cargo test -p knowledge-git-lib --test git_read_path PASS (2)
cargo test -p knowledge-git-lib --test git_write_candidate PASS (2)
cargo test -p knowledge-git-lib --test git_write_push PASS (2)
cargo test --workspace --all-targets --all-features PASS
```
Tests workspace observés :
```text
knowledge-app-lib 3 PASS
knowledge-domain-lib 3 PASS
knowledge-git-api 7 PASS
knowledge-git-lib 8 unit + 6 integration PASS
knowledge-sync-api 1 PASS
knowledge-web-lib 4 PASS
launchers 0 test chacun
```
Les tests réels sur remote bare local prouvent :
- mise à jour d'une branche existante lorsque sa révision correspond exactement à l'attente ;
- création d'une branche uniquement lorsque son absence est attendue ;
- protection compare-and-exchange via `--force-with-lease` ;
- neutralisation locale de `remote.origin.mirror=true` pour le refspec ciblé du push ;
- détection d'une modification distante concurrente ;
- retour d'un conflit sans écrasement de la révision distante concurrente.
## Contrôles spécifiques
Les contrôles courts et indépendants passent tous :
```text
history alpha.3.fix.1 PASS confirmé
pub fn push_candidate présent
--force-with-lease présent
remote.origin.mirror=false présent
GitOperation::InspectRemote présent
GitOperation::PushCandidate présent
impl GitRepositoryWriter for GitBackend présent
tests succès/création et conflit concurrent présents
CHANGELOG 0.0.3 encore absent
history alpha.4 avant gate absent
crates Gitea/PostgreSQL et migrations absentes
aucun diff knowledge-git-api / knowledge-app-lib / Web PASS
aucun diff CHANGELOG.md / ROADMAP.md PASS
Knowledge 0.0.3-alpha.4 text hygiene clean (112 file(s))
Cargo.lock présent localement, ignoré, non suivi
package-lock Web présent
```
Après staging, `git diff --cached --check` passe.
Le staging final observé avant ajout du présent history est exactement :
```text
M Cargo.toml
M README.md
M VERSION
M crates/knowledge-git-lib/src/error.rs
M crates/knowledge-git-lib/src/lib.rs
M crates/knowledge-git-lib/src/write.rs
A crates/knowledge-git-lib/tests/git_write_push.rs
A deltas/0.0.3/alpha.4.md
M docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
M docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
M docs/validation/001-VALIDATION_MATRIX.md
```
## Décision
`0.0.3-alpha.4` est `PASS`.
Le scope fonctionnel alpha de `0.0.3` est fermé : lecture Git, création de candidat et push gardé provider-neutral sont acquis. `0.0.3-beta.1` peut maintenant réconcilier l'architecture et les `DEFER`, préparer le changelog candidat, rejouer la non-régression de stabilisation et effectuer la revue d'horizon produit complète des trois sites PGR / HOWTOs / snippets avant la release candidate.

Some files were not shown because too many files have changed in this diff Show More