Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a15fff86b8 | |||
| 095ac37d08 | |||
| b55eb23dc0 | |||
| b382397244 | |||
| 9a1f73d57b | |||
| 0351aa045e | |||
| 038b91864a | |||
| 282d59cfb7 | |||
| a87f3ba4ab | |||
| aca00e6595 | |||
| c3a825657d | |||
| 88a63b06cc | |||
| 3004e4b581 | |||
| dd63e8578a | |||
| a74a19838f | |||
| 183d354a67 | |||
| 3bb1ffebbe | |||
| 6de544f534 | |||
| 4b51098911 | |||
| 5cee2a65b0 | |||
| da5445354e | |||
| 1fe39dfdeb | |||
| 3f8c795cc0 | |||
| b9508b0228 | |||
| 721e8a6d9c | |||
| 38025266b9 | |||
| ffb47214af | |||
| bdbd6a51a4 | |||
| 84c01eb8e8 | |||
| ab1e5fd652 |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -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 ;
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
# file: Cargo.toml
|
||||
# version: 16
|
||||
# version: 41
|
||||
|
||||
[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",
|
||||
"crates/knowledge-howtos-server",
|
||||
"crates/knowledge-snippets-server",
|
||||
"crates/knowledge-store-postgres-lib",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.1"
|
||||
version = "0.0.4-alpha.2.fix.1"
|
||||
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]
|
||||
|
||||
30
README.md
30
README.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: README.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 27 -->
|
||||
|
||||
# SaseDev Knowledge
|
||||
|
||||
@@ -11,7 +11,13 @@ 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.
|
||||
|
||||
`0.0.4-alpha.2` matérialise la première fondation PostgreSQL partagée. `knowledge-store-postgres-lib` possède désormais une configuration explicite, un pool `deadpool-postgres` sur `tokio-postgres`, des migrations SQL embarquées via Refinery et une première migration créant `knowledge_sources` et `knowledge_source_sync_state`. Aucun ORM, fallback de stockage, accès implicite à l'environnement ou raccord launcher n'est introduit ; Git reste autoritaire pour tout contenu publié.
|
||||
|
||||
`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
|
||||
|
||||
@@ -27,11 +33,11 @@ Ces trois launchers restent minces et réutilisent les mêmes crates communes lo
|
||||
|
||||
Chaque famille possède au minimum sa propre source Git autoritaire : dépôt `pgr`, dépôt `howtos` et dépôt `snippets`. La configuration doit permettre d'ajouter d'autres sources sans figer le système à exactement trois repositories.
|
||||
|
||||
PostgreSQL est mutualisable et constitue la baseline retenue pour l'état transversal de la plateforme : comptes, sessions, propositions, synchronisation, index et relations entre familles. Il ne remplace jamais les sources Git comme vérité du contenu publié.
|
||||
PostgreSQL est mutualisable et constitue la baseline retenue pour l'état transversal de la plateforme : comptes, sessions, propositions, synchronisation, index et relations entre familles. `0.0.4-alpha.2` matérialise le premier owner partagé avec `tokio-postgres 0.7.18`, `deadpool-postgres 0.14.2`, Refinery `0.9.2` et une migration SQL versionnée. La bibliothèque de stockage reçoit sa configuration de connexion depuis la composition, ne lit ni ne journalise elle-même des secrets d'environnement et utilise actuellement `NoTls` sans ajouter de backend TLS tant qu'un déploiement ne l'exige pas. PostgreSQL ne remplace jamais les sources Git comme vérité du contenu publié.
|
||||
|
||||
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 dix 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`, le stockage `knowledge-store-postgres-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 +53,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 +75,25 @@ 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/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md`](docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md) — étude de démarrage PostgreSQL, comparaison des clients/ORM, ownership, premier schéma, migrations et tests ;
|
||||
- [`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 historique de la stable `0.0.3`, écriture Git contrôlée et push gardé ;
|
||||
- [`docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md`](docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md) — plan actif de la fondation PostgreSQL partagée ;
|
||||
- [`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
|
||||
|
||||
|
||||
50
ROADMAP.md
50
ROADMAP.md
@@ -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.
|
||||
|
||||
102
RULES.md
102
RULES.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: RULES.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 17 -->
|
||||
|
||||
# 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.
|
||||
@@ -126,7 +142,17 @@ La baseline de fondation retient une base PostgreSQL logique partagée par les t
|
||||
|
||||
Le texte et les métadonnées éditoriales qui constituent le contenu publié restent autoritaires dans Git. Une copie ou projection PostgreSQL de ces données est dérivée et reconstructible.
|
||||
|
||||
Le choix du driver, de l'ORM ou de la stratégie de migration reste différé jusqu'à une tranche qui en a réellement besoin.
|
||||
`0.0.4-alpha.1` sélectionne `tokio-postgres` comme driver PostgreSQL async natif, `deadpool-postgres` comme pool et Refinery comme mécanisme de migrations. `0.0.4-alpha.2` matérialise ces choix avec `tokio-postgres 0.7.18`, `deadpool-postgres 0.14.2` et Refinery `0.9.2`. Knowledge ne sélectionne pas d'ORM ni d'abstraction multi-SGBD : les requêtes restent SQL explicite derrière un owner de stockage partagé. Chaque dépendance doit être ajoutée avec les fonctionnalités Cargo minimales réellement nécessaires : `runtime` pour `tokio-postgres`, `rt_tokio_1` pour `deadpool-postgres` et `tokio-postgres` pour Refinery dans cette première matérialisation.
|
||||
|
||||
L'owner réel est `knowledge-store-postgres-lib`. Aucun `knowledge-store-api` séparé n'est créé tant qu'une seconde implémentation remplaçable ou une frontière passive indépendante n'est pas démontrée. Les types du driver, du pool et du migrator restent confinés à cette implémentation.
|
||||
|
||||
Les migrations sont des fichiers SQL versionnés possédés par cette crate, appliqués explicitement via Refinery et embarqués avec le binaire. Une migration déjà appliquée est suivie par le mécanisme de migration ; le runtime ne doit pas modifier silencieusement le schéma au fil des requêtes. La première implémentation utilise `tokio_postgres::NoTls` et n'ajoute aucun backend TLS tant qu'une cible de déploiement n'en démontre pas le besoin.
|
||||
|
||||
La bibliothèque de stockage reçoit sa configuration de connexion et de pool depuis sa composition. Elle MUST NOT lire directement une variable d'environnement secrète comme source implicite, MUST NOT journaliser une URL de connexion ou des credentials et MUST retourner une erreur récupérable si la connexion ou une migration échoue. Aucune indisponibilité PostgreSQL ne déclenche un fallback caché vers un fichier ou un stockage mémoire.
|
||||
|
||||
Le premier schéma de `0.0.4` reste minimal : `knowledge_sources` conserve l'identifiant logique, la famille et la référence Git logique ; `knowledge_source_sync_state` conserve uniquement la dernière révision synchronisée. Il ne stocke ni corps de contenu publié, ni credential Git, ni modèle auth/proposition final, ni projection de contenu anticipée.
|
||||
|
||||
À partir d'`0.0.4-alpha.2`, toute gate qui prétend valider les migrations PostgreSQL doit utiliser un PostgreSQL réel fourni explicitement via `KNOWLEDGE_POSTGRES_TEST_URL`. Le test doit isoler son état dans un schéma temporaire et le supprimer après validation ; l'absence du service requis rend la gate `BLOCKED`, jamais `PASS`.
|
||||
|
||||
## Frontières documentaires
|
||||
|
||||
@@ -169,7 +195,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 +203,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 +211,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 +225,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 +244,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
|
||||
|
||||
|
||||
19
crates/knowledge-app-lib/Cargo.toml
Normal file
19
crates/knowledge-app-lib/Cargo.toml
Normal 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
|
||||
14
crates/knowledge-app-lib/src/lib.rs
Normal file
14
crates/knowledge-app-lib/src/lib.rs
Normal 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;
|
||||
89
crates/knowledge-app-lib/src/repository_sync.rs
Normal file
89
crates/knowledge-app-lib/src/repository_sync.rs
Normal 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;
|
||||
121
crates/knowledge-app-lib/unit_tests/repository_sync.rs
Normal file
121
crates/knowledge-app-lib/unit_tests/repository_sync.rs
Normal 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;
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
18
crates/knowledge-git-api/src/reference.rs
Normal file
18
crates/knowledge-git-api/src/reference.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
20
crates/knowledge-git-api/src/repository.rs
Normal file
20
crates/knowledge-git-api/src/repository.rs
Normal 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>;
|
||||
}
|
||||
18
crates/knowledge-git-api/src/revision.rs
Normal file
18
crates/knowledge-git-api/src/revision.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
26
crates/knowledge-git-api/src/snapshot.rs
Normal file
26
crates/knowledge-git-api/src/snapshot.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
319
crates/knowledge-git-api/src/write.rs
Normal file
319
crates/knowledge-git-api/src/write.rs
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
16
crates/knowledge-git-lib/Cargo.toml
Normal file
16
crates/knowledge-git-lib/Cargo.toml
Normal 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
|
||||
674
crates/knowledge-git-lib/src/backend.rs
Normal file
674
crates/knowledge-git-lib/src/backend.rs
Normal 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;
|
||||
150
crates/knowledge-git-lib/src/error.rs
Normal file
150
crates/knowledge-git-lib/src/error.rs
Normal 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 {}
|
||||
33
crates/knowledge-git-lib/src/lib.rs
Normal file
33
crates/knowledge-git-lib/src/lib.rs
Normal 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;
|
||||
505
crates/knowledge-git-lib/src/write.rs
Normal file
505
crates/knowledge-git-lib/src/write.rs
Normal 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(¤t);
|
||||
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;
|
||||
}
|
||||
200
crates/knowledge-git-lib/tests/git_read_path.rs
Normal file
200
crates/knowledge-git-lib/tests/git_read_path.rs
Normal 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;
|
||||
}
|
||||
312
crates/knowledge-git-lib/tests/git_write_candidate.rs
Normal file
312
crates/knowledge-git-lib/tests/git_write_candidate.rs
Normal 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;
|
||||
}
|
||||
282
crates/knowledge-git-lib/tests/git_write_push.rs
Normal file
282
crates/knowledge-git-lib/tests/git_write_push.rs
Normal 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;
|
||||
}
|
||||
303
crates/knowledge-git-lib/unit_tests/backend.rs
Normal file
303
crates/knowledge-git-lib/unit_tests/backend.rs
Normal 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;
|
||||
}
|
||||
20
crates/knowledge-store-postgres-lib/Cargo.toml
Normal file
20
crates/knowledge-store-postgres-lib/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
# file: crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "knowledge-store-postgres-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
deadpool-postgres = { version = "0.14.2", default-features = false, features = ["rt_tokio_1"] }
|
||||
refinery = { version = "0.9.2", default-features = false, features = ["tokio-postgres"] }
|
||||
tokio-postgres = { version = "0.7.18", default-features = false, features = ["runtime"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,13 @@
|
||||
-- file: crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
-- version: 1
|
||||
|
||||
CREATE TABLE knowledge_sources (
|
||||
source_id TEXT PRIMARY KEY,
|
||||
family TEXT NOT NULL CHECK (family IN ('pgr', 'howto', 'snippet', 'project_local')),
|
||||
git_reference TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE knowledge_source_sync_state (
|
||||
source_id TEXT PRIMARY KEY REFERENCES knowledge_sources(source_id) ON DELETE CASCADE,
|
||||
last_synchronized_revision TEXT NOT NULL
|
||||
);
|
||||
39
crates/knowledge-store-postgres-lib/src/config.rs
Normal file
39
crates/knowledge-store-postgres-lib/src/config.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
// file: crates/knowledge-store-postgres-lib/src/config.rs
|
||||
// version: 1
|
||||
|
||||
//! Explicit PostgreSQL pool configuration supplied by application composition.
|
||||
|
||||
/// Configuration required to construct the shared PostgreSQL store.
|
||||
///
|
||||
/// The type intentionally does not implement `Debug` because the database URL may contain a
|
||||
/// credential. Callers must not log or persist the URL as diagnostic output.
|
||||
#[derive(Clone)]
|
||||
pub struct PostgresStoreConfig {
|
||||
database_url: String,
|
||||
max_pool_size: usize,
|
||||
}
|
||||
|
||||
impl crate::PostgresStoreConfig {
|
||||
/// Build a PostgreSQL configuration from an explicit connection URL and maximum pool size.
|
||||
pub fn new(database_url: &str, max_pool_size: usize) -> Result<crate::PostgresStoreConfig, crate::PostgresStoreError> {
|
||||
if database_url.trim().is_empty() {
|
||||
return Err(crate::PostgresStoreError::EmptyDatabaseUrl);
|
||||
}
|
||||
if max_pool_size == 0 {
|
||||
return Err(crate::PostgresStoreError::InvalidPoolSize);
|
||||
}
|
||||
return Ok(Self { database_url: database_url.to_owned(), max_pool_size });
|
||||
}
|
||||
|
||||
pub(crate) fn database_url(&self) -> &str {
|
||||
return self.database_url.as_str();
|
||||
}
|
||||
|
||||
pub(crate) fn max_pool_size(&self) -> usize {
|
||||
return self.max_pool_size;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/config.rs"]
|
||||
mod tests;
|
||||
36
crates/knowledge-store-postgres-lib/src/error.rs
Normal file
36
crates/knowledge-store-postgres-lib/src/error.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
// file: crates/knowledge-store-postgres-lib/src/error.rs
|
||||
// version: 1
|
||||
|
||||
//! Recoverable errors owned by the PostgreSQL storage boundary.
|
||||
|
||||
/// Recoverable failure returned by the PostgreSQL storage foundation.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum PostgresStoreError {
|
||||
/// The explicit database URL is empty or whitespace only.
|
||||
EmptyDatabaseUrl,
|
||||
/// The configured pool size is zero.
|
||||
InvalidPoolSize,
|
||||
/// The database URL cannot be parsed as PostgreSQL connection configuration.
|
||||
InvalidDatabaseConfiguration,
|
||||
/// The connection pool cannot be constructed from the supplied configuration.
|
||||
PoolBuild,
|
||||
/// A PostgreSQL connection cannot be acquired from the pool.
|
||||
PoolAcquisition,
|
||||
/// The embedded migration set cannot be applied.
|
||||
Migration,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for crate::PostgresStoreError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
crate::PostgresStoreError::EmptyDatabaseUrl => return formatter.write_str("PostgreSQL database URL must not be empty"),
|
||||
crate::PostgresStoreError::InvalidPoolSize => return formatter.write_str("PostgreSQL pool size must be greater than zero"),
|
||||
crate::PostgresStoreError::InvalidDatabaseConfiguration => return formatter.write_str("PostgreSQL database configuration is invalid"),
|
||||
crate::PostgresStoreError::PoolBuild => return formatter.write_str("PostgreSQL connection pool could not be constructed"),
|
||||
crate::PostgresStoreError::PoolAcquisition => return formatter.write_str("PostgreSQL connection could not be acquired"),
|
||||
crate::PostgresStoreError::Migration => return formatter.write_str("PostgreSQL migrations could not be applied"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for crate::PostgresStoreError {}
|
||||
16
crates/knowledge-store-postgres-lib/src/lib.rs
Normal file
16
crates/knowledge-store-postgres-lib/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
// file: crates/knowledge-store-postgres-lib/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! PostgreSQL storage foundation shared by SaseDev Knowledge applications.
|
||||
//!
|
||||
//! The crate owns explicit PostgreSQL configuration, the connection pool and versioned migrations.
|
||||
//! It does not read process environment variables, expose driver types through its public API or
|
||||
//! replace Git as the authority for published content.
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod store;
|
||||
|
||||
pub use crate::config::PostgresStoreConfig;
|
||||
pub use crate::error::PostgresStoreError;
|
||||
pub use crate::store::PostgresStore;
|
||||
44
crates/knowledge-store-postgres-lib/src/store.rs
Normal file
44
crates/knowledge-store-postgres-lib/src/store.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// file: crates/knowledge-store-postgres-lib/src/store.rs
|
||||
// version: 1
|
||||
|
||||
//! Connection-pool ownership and explicit migration execution.
|
||||
|
||||
mod embedded {
|
||||
refinery::embed_migrations!();
|
||||
}
|
||||
|
||||
/// Shared PostgreSQL store backed by a bounded Deadpool connection pool.
|
||||
#[derive(Clone)]
|
||||
pub struct PostgresStore {
|
||||
pool: deadpool_postgres::Pool,
|
||||
}
|
||||
|
||||
impl crate::PostgresStore {
|
||||
/// Construct the PostgreSQL store without reading process environment variables.
|
||||
pub fn new(config: crate::PostgresStoreConfig) -> Result<crate::PostgresStore, crate::PostgresStoreError> {
|
||||
let postgres_config = match config.database_url().parse::<tokio_postgres::Config>() {
|
||||
Ok(value) => value,
|
||||
Err(_) => return Err(crate::PostgresStoreError::InvalidDatabaseConfiguration),
|
||||
};
|
||||
let manager = deadpool_postgres::Manager::new(postgres_config, tokio_postgres::NoTls);
|
||||
let pool = match deadpool_postgres::Pool::builder(manager).max_size(config.max_pool_size()).build() {
|
||||
Ok(value) => value,
|
||||
Err(_) => return Err(crate::PostgresStoreError::PoolBuild),
|
||||
};
|
||||
return Ok(Self { pool });
|
||||
}
|
||||
|
||||
/// Apply every embedded versioned migration that has not yet been recorded by Refinery.
|
||||
pub async fn apply_migrations(&self) -> Result<(), crate::PostgresStoreError> {
|
||||
let mut connection = match self.pool.get().await {
|
||||
Ok(value) => value,
|
||||
Err(_) => return Err(crate::PostgresStoreError::PoolAcquisition),
|
||||
};
|
||||
let wrapper = std::ops::DerefMut::deref_mut(&mut connection);
|
||||
let client = std::ops::DerefMut::deref_mut(wrapper);
|
||||
match embedded::migrations::runner().run_async(client).await {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(_) => return Err(crate::PostgresStoreError::Migration),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// file: crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
// version: 2
|
||||
|
||||
//! Integration test for the initial PostgreSQL migration in an isolated schema.
|
||||
|
||||
fn test_schema_name() -> String {
|
||||
let elapsed = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return format!("knowledge_test_{}", std::process::id()),
|
||||
};
|
||||
return format!("knowledge_test_{}_{}", std::process::id(), elapsed.as_nanos());
|
||||
}
|
||||
|
||||
fn database_url_with_search_path(database_url: &str, schema: &str) -> String {
|
||||
let separator = if database_url.contains('?') { '&' } else { '?' };
|
||||
return format!("{database_url}{separator}options=-c%20search_path%3D{schema}");
|
||||
}
|
||||
|
||||
async fn connect(database_url: &str) -> Option<tokio_postgres::Client> {
|
||||
let connection_result = tokio_postgres::connect(database_url, tokio_postgres::NoTls).await;
|
||||
assert!(connection_result.is_ok(), "test PostgreSQL connection failed");
|
||||
let (client, connection) = match connection_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let _connection_task = tokio::spawn(async move {
|
||||
let _connection_result = connection.await;
|
||||
return;
|
||||
});
|
||||
return Some(client);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires KNOWLEDGE_POSTGRES_TEST_URL"]
|
||||
async fn initial_migration_runs_in_isolated_schema() {
|
||||
let database_url_result = std::env::var("KNOWLEDGE_POSTGRES_TEST_URL");
|
||||
assert!(database_url_result.is_ok(), "KNOWLEDGE_POSTGRES_TEST_URL is required");
|
||||
let database_url = match database_url_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let schema = test_schema_name();
|
||||
let admin = match connect(database_url.as_str()).await {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
let create_schema = format!("CREATE SCHEMA {schema}");
|
||||
let create_result = admin.batch_execute(create_schema.as_str()).await;
|
||||
assert!(create_result.is_ok(), "isolated PostgreSQL schema creation failed");
|
||||
if create_result.is_err() {
|
||||
return;
|
||||
}
|
||||
let isolated_url = database_url_with_search_path(database_url.as_str(), schema.as_str());
|
||||
let config_result = knowledge_store_postgres_lib::PostgresStoreConfig::new(isolated_url.as_str(), 2);
|
||||
assert!(config_result.is_ok(), "PostgreSQL store configuration failed");
|
||||
let config = match config_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let store_result = knowledge_store_postgres_lib::PostgresStore::new(config);
|
||||
assert!(store_result.is_ok(), "PostgreSQL store construction failed");
|
||||
let store = match store_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let migration_result = store.apply_migrations().await;
|
||||
assert!(migration_result.is_ok(), "initial PostgreSQL migration failed");
|
||||
let table_result = admin
|
||||
.query(
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_name IN ('knowledge_sources', 'knowledge_source_sync_state', 'refinery_schema_history') ORDER BY table_name",
|
||||
&[&schema],
|
||||
)
|
||||
.await;
|
||||
assert!(table_result.is_ok(), "PostgreSQL migration verification query failed");
|
||||
let table_rows = match table_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
assert_eq!(table_rows.len(), 3);
|
||||
if table_rows.len() == 3 {
|
||||
assert_eq!(table_rows[0].get::<usize, &str>(0), "knowledge_source_sync_state");
|
||||
assert_eq!(table_rows[1].get::<usize, &str>(0), "knowledge_sources");
|
||||
assert_eq!(table_rows[2].get::<usize, &str>(0), "refinery_schema_history");
|
||||
}
|
||||
drop(store);
|
||||
let drop_schema = format!("DROP SCHEMA {schema} CASCADE");
|
||||
let drop_result = admin.batch_execute(drop_schema.as_str()).await;
|
||||
assert!(drop_result.is_ok(), "isolated PostgreSQL schema cleanup failed");
|
||||
return;
|
||||
}
|
||||
23
crates/knowledge-store-postgres-lib/unit_tests/config.rs
Normal file
23
crates/knowledge-store-postgres-lib/unit_tests/config.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// file: crates/knowledge-store-postgres-lib/unit_tests/config.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn config_rejects_empty_database_url() {
|
||||
let result = crate::PostgresStoreConfig::new(" ", 4);
|
||||
assert!(matches!(result, Err(crate::PostgresStoreError::EmptyDatabaseUrl)));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_rejects_zero_pool_size() {
|
||||
let result = crate::PostgresStoreConfig::new("postgresql://localhost/knowledge", 0);
|
||||
assert!(matches!(result, Err(crate::PostgresStoreError::InvalidPoolSize)));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_accepts_explicit_non_empty_values() {
|
||||
let result = crate::PostgresStoreConfig::new("postgresql://localhost/knowledge", 4);
|
||||
assert!(result.is_ok());
|
||||
return;
|
||||
}
|
||||
14
crates/knowledge-sync-api/src/event_source.rs
Normal file
14
crates/knowledge-sync-api/src/event_source.rs
Normal 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>;
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
37
crates/knowledge-sync-api/src/signal.rs
Normal file
37
crates/knowledge-sync-api/src/signal.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
204
deltas/0.0.2/alpha.1.fix.1.md
Normal file
204
deltas/0.0.2/alpha.1.fix.1.md
Normal 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
185
deltas/0.0.2/alpha.1.md
Normal 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.
|
||||
280
deltas/0.0.2/alpha.2.fix.1.md
Normal file
280
deltas/0.0.2/alpha.2.fix.1.md
Normal 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.
|
||||
183
deltas/0.0.2/alpha.2.fix.2.md
Normal file
183
deltas/0.0.2/alpha.2.fix.2.md
Normal 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
324
deltas/0.0.2/alpha.2.md
Normal 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.
|
||||
262
deltas/0.0.2/alpha.3.fix.1.md
Normal file
262
deltas/0.0.2/alpha.3.fix.1.md
Normal 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
298
deltas/0.0.2/alpha.3.md
Normal 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`.
|
||||
171
deltas/0.0.2/alpha.4.fix.1.md
Normal file
171
deltas/0.0.2/alpha.4.fix.1.md
Normal 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
283
deltas/0.0.2/alpha.4.md
Normal 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
138
deltas/0.0.2/alpha.5.md
Normal 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.
|
||||
270
deltas/0.0.2/beta.1.fix.1.md
Normal file
270
deltas/0.0.2/beta.1.fix.1.md
Normal 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.
|
||||
161
deltas/0.0.2/beta.1.fix.2.md
Normal file
161
deltas/0.0.2/beta.1.fix.2.md
Normal 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
168
deltas/0.0.2/beta.1.md
Normal 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
319
deltas/0.0.2/rc.1.md
Normal 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
335
deltas/0.0.2/rel.1.md
Normal 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`.
|
||||
184
deltas/0.0.3/alpha.1.fix.1.md
Normal file
184
deltas/0.0.3/alpha.1.fix.1.md
Normal 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.
|
||||
173
deltas/0.0.3/alpha.1.fix.2.md
Normal file
173
deltas/0.0.3/alpha.1.fix.2.md
Normal 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
288
deltas/0.0.3/alpha.1.md
Normal 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
232
deltas/0.0.3/alpha.2.md
Normal 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é.
|
||||
173
deltas/0.0.3/alpha.3.fix.1.md
Normal file
173
deltas/0.0.3/alpha.3.fix.1.md
Normal 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
256
deltas/0.0.3/alpha.3.md
Normal 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
186
deltas/0.0.3/alpha.4.md
Normal 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
207
deltas/0.0.3/beta.1.md
Normal 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
154
deltas/0.0.3/rc.1.fix.1.md
Normal 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
253
deltas/0.0.3/rc.1.md
Normal 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
112
deltas/0.0.3/rel.1.fix.1.md
Normal 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
77
deltas/0.0.3/rel.1.md
Normal 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.
|
||||
217
deltas/0.0.4/alpha.1.md
Normal file
217
deltas/0.0.4/alpha.1.md
Normal file
@@ -0,0 +1,217 @@
|
||||
<!-- file: deltas/0.0.4/alpha.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.0.4-alpha.1 — cadrage de la fondation PostgreSQL partagée
|
||||
|
||||
## Baseline requise
|
||||
|
||||
Partir de la stable autoritaire réellement publiée :
|
||||
|
||||
```text
|
||||
tag Knowledge v0.0.3
|
||||
commit Knowledge b3823972449150b72905930b806d4d15471cddce
|
||||
archive SHA-256 82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d
|
||||
publish report sasedev-knowledge-0.0.3-publish.001-report.md = PASS
|
||||
start prompt prompts/004-V0_0_4_START_PROMPT.md
|
||||
```
|
||||
|
||||
Le rapport post-tag fourni confirme la convergence `HEAD/tag distant/commentaire ZIP`, l'intégrité de l'archive, les douze deltas/histories `0.0.3` et `rel.1.fix.1 = PASS`.
|
||||
|
||||
PGR `v0.1.3` reste une provenance historique de l'adoption fermée en `0.0.2`. Aucune nouvelle version PGR n'est chargée ou adoptée.
|
||||
|
||||
La version devient :
|
||||
|
||||
```text
|
||||
0.0.4-alpha.1
|
||||
```
|
||||
|
||||
## Objet
|
||||
|
||||
Fermer le cadrage PostgreSQL avant toute implémentation :
|
||||
|
||||
- vérifier la stable `v0.0.3`, ses owners, architecture, deltas/histories, `DEFER` et revue d'horizon ;
|
||||
- comparer les clients/ORM/migrations Rust actuels ;
|
||||
- sélectionner `tokio-postgres` comme driver, `deadpool-postgres` comme pool et Refinery comme migrator, sans ORM ;
|
||||
- retenir `knowledge-store-postgres-lib` comme owner futur du pool, des migrations et de l'état PostgreSQL ;
|
||||
- borner le premier schéma à `knowledge_sources` et `knowledge_source_sync_state` ;
|
||||
- définir une configuration injectée sans fuite de secrets et sans fallback caché ;
|
||||
- définir une stratégie de tests PostgreSQL réels par schéma éphémère ;
|
||||
- préparer l'étude `008` et le plan `004` ;
|
||||
- conserver l'horizon produit existant puisqu'aucune dépendance future n'est invalidée.
|
||||
|
||||
Aucun code produit, aucune nouvelle crate, aucune dépendance Cargo/npm, aucune migration SQL et aucun fichier Web ne sont ajoutés dans cette tranche.
|
||||
|
||||
`CHANGELOG.md` reste inchangé conformément au prompt de démarrage : l'entrée `0.0.4` ne sera préparée qu'à l'approche de la stabilisation.
|
||||
|
||||
## Décisions retenues
|
||||
|
||||
```text
|
||||
owner PostgreSQL knowledge-store-postgres-lib (à créer en alpha.2)
|
||||
driver tokio-postgres 0.7
|
||||
ORM aucun
|
||||
pool deadpool-postgres 0.14
|
||||
migrations SQL versionné + Refinery 0.9
|
||||
premier schéma source logique + dernière révision synchronisée
|
||||
test PostgreSQL URL explicite + schéma éphémère isolé
|
||||
autorité contenu Git
|
||||
```
|
||||
|
||||
Les types de `tokio-postgres`, Deadpool et Refinery restent confinés à `knowledge-store-postgres-lib`. Un backend TLS sera choisi seulement si la connexion de déploiement réellement visée l'exige.
|
||||
|
||||
## Horizon
|
||||
|
||||
Aucun REPLAN d'horizon n'est nécessaire.
|
||||
|
||||
Restent :
|
||||
|
||||
```text
|
||||
0.0.5 modèles de contenu et projections
|
||||
0.0.6 templates publics
|
||||
0.0.7 templates d'administration
|
||||
0.0.8 lecture publique Actix Web / Maud
|
||||
0.0.9 administration Actix Web / Maud et autorisation
|
||||
0.0.10 gestion, proposition, revue et publication
|
||||
```
|
||||
|
||||
Gitea/forge, scheduler, recherche avancée, auth, modèles finaux de contenu, administration transverse et sujets Git avancés restent `DEFER`.
|
||||
|
||||
## Scope des fichiers
|
||||
|
||||
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/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
deltas/0.0.4/alpha.1.md
|
||||
```
|
||||
|
||||
DELETE : aucun.
|
||||
|
||||
`ROADMAP.md`, `CHANGELOG.md`, `prompts/`, `crates/` et `Web/` ne sont pas modifiés.
|
||||
|
||||
Aucun history `0.0.4-alpha.1` n'est inclus : il doit refléter uniquement la gate opérateur réelle.
|
||||
|
||||
## Gate opérateur
|
||||
|
||||
Exécuter depuis la racine du dépôt après application du delta.
|
||||
|
||||
### Version et baseline
|
||||
|
||||
```bash
|
||||
test "$(cat VERSION)" = '0.0.4-alpha.1'
|
||||
grep -F 'version = "0.0.4-alpha.1"' Cargo.toml
|
||||
|
||||
grep -F 'Knowledge stable tag v0.0.3' docs/architecture/001-PROJECT_PROFILE.md
|
||||
grep -F 'Knowledge stable commit b3823972449150b72905930b806d4d15471cddce' docs/architecture/001-PROJECT_PROFILE.md
|
||||
grep -F 'Knowledge archive SHA-256 82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d' docs/architecture/001-PROJECT_PROFILE.md
|
||||
grep -F 'Knowledge publish report sasedev-knowledge-0.0.3-publish.001-report.md = PASS' docs/architecture/001-PROJECT_PROFILE.md
|
||||
|
||||
test "$(find deltas/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 12
|
||||
test "$(find history/0.0.3 -maxdepth 1 -type f -name '*.md' | wc -l)" -eq 12
|
||||
diff <(find deltas/0.0.3 -maxdepth 1 -type f -name '*.md' -printf '%f\n' | sort) <(find history/0.0.3 -maxdepth 1 -type f -name '*.md' -printf '%f\n' | sort)
|
||||
grep -A2 '^## Statut$' history/0.0.3/rel.1.fix.1.md | grep -Fx 'PASS'
|
||||
```
|
||||
|
||||
### Cadrage PostgreSQL et autonomie
|
||||
|
||||
```bash
|
||||
test -f docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
test -f docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
test -f deltas/0.0.4/alpha.1.md
|
||||
test ! -e history/0.0.4/alpha.1.md
|
||||
|
||||
grep -F '`tokio-postgres` comme driver PostgreSQL' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F '`deadpool-postgres` comme pool' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F '`refinery` comme owner technique des migrations SQL' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F 'knowledge-store-postgres-lib' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F 'knowledge_sources' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F 'knowledge_source_sync_state' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
grep -F 'KNOWLEDGE_POSTGRES_TEST_URL' docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
|
||||
grep -F 'Ne pas rechercher, charger ni adopter automatiquement une nouvelle version de PGR.' prompts/004-V0_0_4_START_PROMPT.md
|
||||
grep -F 'PGR `v0.1.3` reste une provenance historique' docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
|
||||
test ! -e crates/knowledge-store-postgres-lib
|
||||
test ! -e migrations
|
||||
test ! -e history/0.0.4/alpha.1.md
|
||||
test -z "$(git status --short -- crates Web CHANGELOG.md ROADMAP.md prompts)"
|
||||
```
|
||||
|
||||
### Documentation puis formatage/audits Rust
|
||||
|
||||
```bash
|
||||
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.4-alpha.1-metadata.json
|
||||
cargo check --workspace
|
||||
```
|
||||
|
||||
`alpha.1` ne rejoue pas Clippy, tests workspace, npm/Vite ou smokes HTTP : aucun source Rust, dépendance, frontend ou runtime n'est modifié. `cargo check --workspace` vérifie néanmoins le manifest après promotion de version.
|
||||
|
||||
### Structure, horizon et hygiène
|
||||
|
||||
```bash
|
||||
python3 -c 'import json; from pathlib import Path; m=json.loads(Path("/tmp/sasedev-knowledge-0.0.4-alpha.1-metadata.json").read_text()); assert len(m["workspace_members"]) == 9; assert {p["name"] for p in m["packages"]} == {"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"}; print("Knowledge 0.0.4-alpha.1 workspace audit: clean")'
|
||||
|
||||
grep -F -- '- `( ) 0.0.4` — fondation PostgreSQL partagée' ROADMAP.md
|
||||
grep -F -- '- `(h) 0.0.5` — modèles de contenu et projections' ROADMAP.md
|
||||
grep -F -- '- `(h) 0.0.10` — gestion, proposition, revue et publication' ROADMAP.md
|
||||
|
||||
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())]; assert not bad, bad; print(f"Knowledge 0.0.4-alpha.1 text hygiene: clean ({len(paths)} file(s))")'
|
||||
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 séparément :
|
||||
|
||||
```text
|
||||
history/0.0.4/alpha.1.md
|
||||
```
|
||||
|
||||
avec les résultats réellement observés. Après application du history, commit/push `0.0.4-alpha.1`, puis seulement préparer `0.0.4-alpha.2` qui matérialisera la crate PostgreSQL, `tokio-postgres`, `deadpool-postgres`, Refinery et la première migration.
|
||||
|
||||
### FAIL / PARTIAL / BLOCKED
|
||||
|
||||
Produire le history réel, l'appliquer et le committer avant toute correction.
|
||||
|
||||
Une correction bornée au cadrage utilise `0.0.4-alpha.1.fix.1`. Si le diagnostic invalide le driver/pool/migrator retenu, l'ownership, le schéma ou la stratégie de test, exécuter un REPLAN plutôt que d'absorber une architecture différente dans un fix.
|
||||
179
deltas/0.0.4/alpha.2.fix.1.md
Normal file
179
deltas/0.0.4/alpha.2.fix.1.md
Normal file
@@ -0,0 +1,179 @@
|
||||
<!-- file: deltas/0.0.4/alpha.2.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.0.4-alpha.2.fix.1 — documentation de la crate de test PostgreSQL
|
||||
|
||||
## Baseline requise
|
||||
|
||||
Partir de `0.0.4-alpha.2` après application, commit et push de son history `FAIL`.
|
||||
|
||||
La version devient :
|
||||
|
||||
```text
|
||||
0.0.4-alpha.2.fix.1
|
||||
```
|
||||
|
||||
Le history requis est :
|
||||
|
||||
```text
|
||||
history/0.0.4/alpha.2.md = FAIL
|
||||
```
|
||||
|
||||
## Objet
|
||||
|
||||
Corriger uniquement le défaut Clippy observé pendant la gate `alpha.2` : la target d'intégration `tests/postgres_migration.rs` ne possède pas de documentation de crate alors que `missing_docs` est activé et promu en erreur par `-D warnings`.
|
||||
|
||||
Le fix ajoute une documentation interne `//!` en tête de cette crate de test.
|
||||
|
||||
Aucun code produit, contrat public, dépendance, configuration de pool, migration SQL ou comportement PostgreSQL n'est modifié. La preuve PostgreSQL réelle de `alpha.2` a déjà passé après fourniture explicite de `KNOWLEDGE_POSTGRES_TEST_URL`, mais elle est rejouée pour fermer complètement le fix.
|
||||
|
||||
## Scope des fichiers
|
||||
|
||||
REPLACE :
|
||||
|
||||
```text
|
||||
VERSION
|
||||
Cargo.toml
|
||||
crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
```
|
||||
|
||||
ADD :
|
||||
|
||||
```text
|
||||
deltas/0.0.4/alpha.2.fix.1.md
|
||||
```
|
||||
|
||||
DELETE : aucun.
|
||||
|
||||
Les fichiers suivants restent inchangés :
|
||||
|
||||
```text
|
||||
crates/knowledge-store-postgres-lib/src/
|
||||
crates/knowledge-store-postgres-lib/unit_tests/
|
||||
crates/knowledge-store-postgres-lib/migrations/
|
||||
crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
README.md
|
||||
RULES.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
docs/architecture/
|
||||
docs/studies/
|
||||
docs/validation/
|
||||
Web/
|
||||
prompts/
|
||||
```
|
||||
|
||||
Aucun history `alpha.2.fix.1` n'est inclus dans ce delta.
|
||||
|
||||
## Précondition PostgreSQL
|
||||
|
||||
La gate requiert toujours une base PostgreSQL de test permettant de créer et supprimer un schéma.
|
||||
|
||||
Avant la preuve PostgreSQL, définir explicitement dans le shell opérateur :
|
||||
|
||||
```bash
|
||||
export KNOWLEDGE_POSTGRES_TEST_URL='postgresql://knowledge:knowledge@localhost/knowledge'
|
||||
```
|
||||
|
||||
Adapter l'URL si nécessaire. Ne jamais versionner cette valeur.
|
||||
|
||||
Si PostgreSQL n'est pas disponible, classer la gate `BLOCKED` à cette étape ; ne pas convertir cette absence en `PASS`.
|
||||
|
||||
## Gate opérateur
|
||||
|
||||
Exécuter depuis la racine du dépôt après application du delta.
|
||||
|
||||
### Version, history précédent et correction bornée
|
||||
|
||||
```bash
|
||||
test "$(cat VERSION)" = '0.0.4-alpha.2.fix.1'
|
||||
grep -F 'version = "0.0.4-alpha.2.fix.1"' Cargo.toml
|
||||
|
||||
test -f history/0.0.4/alpha.2.md
|
||||
grep -A2 '^## Statut$' history/0.0.4/alpha.2.md | grep -Fx 'FAIL'
|
||||
git ls-files --error-unmatch history/0.0.4/alpha.2.md >/dev/null
|
||||
test -z "$(git status --short -- history/0.0.4/alpha.2.md)"
|
||||
|
||||
grep -Fx '//! Integration test for the initial PostgreSQL migration in an isolated schema.' crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
|
||||
grep -F '### alpha.2.fix.1 — documentation de la crate de test PostgreSQL' docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
grep -F '`alpha.2` historisée `FAIL`' docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
grep -F 'autoriser `alpha.3` uniquement après `PASS` complet du fix' docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
|
||||
test ! -e history/0.0.4/alpha.2.fix.1.md
|
||||
|
||||
git diff --check
|
||||
```
|
||||
|
||||
### Scope négatif
|
||||
|
||||
```bash
|
||||
test -z "$(git diff --name-only -- crates/knowledge-store-postgres-lib/src crates/knowledge-store-postgres-lib/unit_tests crates/knowledge-store-postgres-lib/migrations crates/knowledge-store-postgres-lib/Cargo.toml README.md RULES.md ROADMAP.md CHANGELOG.md docs/architecture docs/studies docs/validation Web prompts)"
|
||||
```
|
||||
|
||||
### Markdown puis séquence Rust
|
||||
|
||||
```bash
|
||||
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.4-alpha.2.fix.1-metadata.json
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test -p knowledge-store-postgres-lib
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
### Composition workspace
|
||||
|
||||
```bash
|
||||
python3 -c 'import json; from pathlib import Path; m=json.loads(Path("/tmp/sasedev-knowledge-0.0.4-alpha.2.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","knowledge-store-postgres-lib"}; assert len(m["workspace_members"]) == 10; assert {p["name"] for p in m["packages"]} == expected; print("Knowledge 0.0.4-alpha.2.fix.1 workspace audit: clean")'
|
||||
```
|
||||
|
||||
### PostgreSQL réel obligatoire
|
||||
|
||||
```bash
|
||||
test -n "${KNOWLEDGE_POSTGRES_TEST_URL:-}"
|
||||
cargo test -p knowledge-store-postgres-lib --test postgres_migration -- --ignored --nocapture
|
||||
```
|
||||
|
||||
### Hygiène, lockfiles et staging
|
||||
|
||||
```bash
|
||||
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("Cargo.toml"),Path("VERSION"),Path("docs"),Path("prompts"),Path("deltas"),Path("history"),Path("crates")]; allowed={".md",".rs",".toml",".sql"}; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file() and (p.name == "VERSION" or p.suffix in allowed)]; 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())]; assert not bad, bad; print(f"Knowledge 0.0.4-alpha.2.fix.1 text hygiene: clean ({len(paths)} file(s))")'
|
||||
|
||||
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 diff --quiet -- Web/knowledge-template-lab/package-lock.json
|
||||
|
||||
git add -A
|
||||
git diff --cached --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
## Transition
|
||||
|
||||
### PASS
|
||||
|
||||
Produire `history/0.0.4/alpha.2.fix.1.md`, l'appliquer, commit/push `0.0.4-alpha.2.fix.1`, puis préparer `0.0.4-alpha.3` pour la persistance minimale de source et d'état de synchronisation.
|
||||
|
||||
### FAIL / PARTIAL / BLOCKED
|
||||
|
||||
Conserver le résultat réel dans son history avant toute nouvelle correction. Un défaut borné supplémentaire utilise `alpha.2.fix.2`. Si le diagnostic invalide l'ownership PostgreSQL, les dépendances retenues, le schéma ou la stratégie d'isolation, effectuer un REPLAN avant de poursuivre.
|
||||
279
deltas/0.0.4/alpha.2.md
Normal file
279
deltas/0.0.4/alpha.2.md
Normal file
@@ -0,0 +1,279 @@
|
||||
<!-- file: deltas/0.0.4/alpha.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.0.4-alpha.2 — owner PostgreSQL, pool et première migration
|
||||
|
||||
## Baseline requise
|
||||
|
||||
Partir de `0.0.4-alpha.1` validée et historisée `PASS` :
|
||||
|
||||
```text
|
||||
version précédente 0.0.4-alpha.1
|
||||
history requis history/0.0.4/alpha.1.md = PASS
|
||||
stable autoritaire v0.0.3
|
||||
owner décidé knowledge-store-postgres-lib
|
||||
driver décidé tokio-postgres
|
||||
pool décidé deadpool-postgres
|
||||
migrator décidé Refinery
|
||||
```
|
||||
|
||||
Le history `alpha.1` doit être committé et poussé avant application du présent delta.
|
||||
|
||||
La version devient :
|
||||
|
||||
```text
|
||||
0.0.4-alpha.2
|
||||
```
|
||||
|
||||
## Objet
|
||||
|
||||
Matérialiser la première frontière PostgreSQL réelle sans encore raccorder les launchers ou les cas d'usage applicatifs :
|
||||
|
||||
- créer `knowledge-store-postgres-lib` comme unique owner PostgreSQL ;
|
||||
- ajouter `tokio-postgres 0.7.18` comme driver async natif avec uniquement la feature `runtime` ;
|
||||
- ajouter `deadpool-postgres 0.14.2` comme pool avec uniquement `rt_tokio_1` ;
|
||||
- ajouter Refinery `0.9.2` avec uniquement son support `tokio-postgres` ;
|
||||
- utiliser Tokio `1.53.1` uniquement comme runtime de test ;
|
||||
- recevoir URL PostgreSQL et taille du pool par configuration injectée ;
|
||||
- ne jamais lire de secret depuis l'environnement dans la crate produit ;
|
||||
- construire un pool borné et conserver `NoTls` comme choix initial explicite ;
|
||||
- embarquer `V1__initial_sources.sql` via Refinery ;
|
||||
- créer uniquement `knowledge_sources` et `knowledge_source_sync_state` ;
|
||||
- exposer une application explicite des migrations ;
|
||||
- prouver le chemin sur un PostgreSQL réel isolé par schéma temporaire.
|
||||
|
||||
Git reste l'autorité du contenu publié. Cette tranche ne stocke ni corps de contenu, ni credential Git, ni compte, proposition, job, index ou projection finale.
|
||||
|
||||
## Dépendances retenues
|
||||
|
||||
```text
|
||||
tokio-postgres 0.7.18 default-features = false, features = ["runtime"]
|
||||
deadpool-postgres 0.14.2 default-features = false, features = ["rt_tokio_1"]
|
||||
refinery 0.9.2 default-features = false, features = ["tokio-postgres"]
|
||||
tokio 1.53.1 dev only, features = ["macros", "rt-multi-thread"]
|
||||
```
|
||||
|
||||
Aucun ORM, TLS backend supplémentaire ou abstraction multi-SGBD n'est introduit.
|
||||
|
||||
## Contrat public
|
||||
|
||||
La façade crate-root est limitée à :
|
||||
|
||||
```text
|
||||
PostgresStoreConfig
|
||||
PostgresStore
|
||||
PostgresStoreError
|
||||
```
|
||||
|
||||
Les types `tokio-postgres`, Deadpool et Refinery restent privés à l'implémentation.
|
||||
|
||||
`PostgresStoreConfig` ne fournit aucun getter public de l'URL et n'implémente volontairement pas `Debug`. L'URL peut contenir des credentials et ne doit pas être exposée dans les erreurs ou logs.
|
||||
|
||||
## Premier schéma
|
||||
|
||||
```text
|
||||
knowledge_sources
|
||||
source_id TEXT PRIMARY KEY
|
||||
family TEXT NOT NULL CHECK pgr/howto/snippet/project_local
|
||||
git_reference TEXT NOT NULL
|
||||
|
||||
knowledge_source_sync_state
|
||||
source_id TEXT PRIMARY KEY REFERENCES knowledge_sources ON DELETE CASCADE
|
||||
last_synchronized_revision TEXT NOT NULL
|
||||
```
|
||||
|
||||
Refinery possède sa table technique de suivi des migrations. Aucune autre table métier n'est créée.
|
||||
|
||||
## Scope des fichiers
|
||||
|
||||
REPLACE :
|
||||
|
||||
```text
|
||||
VERSION
|
||||
Cargo.toml
|
||||
README.md
|
||||
RULES.md
|
||||
docs/architecture/001-PROJECT_PROFILE.md
|
||||
docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md
|
||||
docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
|
||||
docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
|
||||
docs/validation/001-VALIDATION_MATRIX.md
|
||||
```
|
||||
|
||||
ADD :
|
||||
|
||||
```text
|
||||
crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
crates/knowledge-store-postgres-lib/src/lib.rs
|
||||
crates/knowledge-store-postgres-lib/src/config.rs
|
||||
crates/knowledge-store-postgres-lib/src/error.rs
|
||||
crates/knowledge-store-postgres-lib/src/store.rs
|
||||
crates/knowledge-store-postgres-lib/unit_tests/config.rs
|
||||
crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
deltas/0.0.4/alpha.2.md
|
||||
```
|
||||
|
||||
DELETE : aucun.
|
||||
|
||||
Aucun fichier sous `Web/`, les trois launchers, `knowledge-app-lib`, `knowledge-domain-lib`, `knowledge-git-api`, `knowledge-git-lib` ou `knowledge-sync-api` n'est modifié.
|
||||
|
||||
Aucun history `0.0.4-alpha.2` n'est inclus : il doit refléter uniquement la gate opérateur réelle.
|
||||
|
||||
## Précondition PostgreSQL de la gate
|
||||
|
||||
La gate réelle nécessite une base PostgreSQL de test accessible avec les droits de créer et supprimer un schéma. Fournir l'URL uniquement dans l'environnement opérateur :
|
||||
|
||||
```bash
|
||||
export KNOWLEDGE_POSTGRES_TEST_URL='postgresql://...'
|
||||
```
|
||||
|
||||
Ne jamais versionner cette valeur.
|
||||
|
||||
Si `KNOWLEDGE_POSTGRES_TEST_URL` est absent ou si PostgreSQL n'est pas joignable, arrêter la gate et classer la tranche `BLOCKED`. Ne pas convertir cette absence en `PASS` ou en test ignoré.
|
||||
|
||||
## Gate opérateur
|
||||
|
||||
Exécuter depuis la racine du dépôt après application du delta.
|
||||
|
||||
### Version, history précédent et scope
|
||||
|
||||
```bash
|
||||
test "$(cat VERSION)" = '0.0.4-alpha.2'
|
||||
grep -F 'version = "0.0.4-alpha.2"' Cargo.toml
|
||||
|
||||
test -f history/0.0.4/alpha.1.md
|
||||
grep -A2 '^## Statut$' history/0.0.4/alpha.1.md | grep -Fx 'PASS'
|
||||
git ls-files --error-unmatch history/0.0.4/alpha.1.md >/dev/null
|
||||
test -z "$(git status --short -- history/0.0.4/alpha.1.md)"
|
||||
|
||||
test -d crates/knowledge-store-postgres-lib
|
||||
test -f crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
test -f deltas/0.0.4/alpha.2.md
|
||||
test ! -e history/0.0.4/alpha.2.md
|
||||
|
||||
test -z "$(git status --short -- Web crates/knowledge-pgr-server crates/knowledge-howtos-server crates/knowledge-snippets-server crates/knowledge-app-lib crates/knowledge-domain-lib crates/knowledge-git-api crates/knowledge-git-lib crates/knowledge-sync-api)"
|
||||
|
||||
git diff --check
|
||||
```
|
||||
|
||||
### Dépendances, façade et configuration
|
||||
|
||||
```bash
|
||||
grep -F 'tokio-postgres = { version = "0.7.18", default-features = false, features = ["runtime"] }' crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
grep -F 'deadpool-postgres = { version = "0.14.2", default-features = false, features = ["rt_tokio_1"] }' crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
grep -F 'refinery = { version = "0.9.2", default-features = false, features = ["tokio-postgres"] }' crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
grep -F 'tokio = { version = "1.53.1", default-features = false, features = ["macros", "rt-multi-thread"] }' crates/knowledge-store-postgres-lib/Cargo.toml
|
||||
|
||||
test "$(grep -c '^pub use crate::' crates/knowledge-store-postgres-lib/src/lib.rs)" -eq 3
|
||||
grep -Fx 'pub use crate::config::PostgresStoreConfig;' crates/knowledge-store-postgres-lib/src/lib.rs
|
||||
grep -Fx 'pub use crate::error::PostgresStoreError;' crates/knowledge-store-postgres-lib/src/lib.rs
|
||||
grep -Fx 'pub use crate::store::PostgresStore;' crates/knowledge-store-postgres-lib/src/lib.rs
|
||||
|
||||
! grep -R -n -E 'std::env|KNOWLEDGE_POSTGRES_TEST_URL' crates/knowledge-store-postgres-lib/src
|
||||
! grep -R -n -E 'println!|eprintln!|dbg!' crates/knowledge-store-postgres-lib/src
|
||||
|
||||
grep -F 'max_pool_size' crates/knowledge-store-postgres-lib/src/config.rs
|
||||
grep -F 'deadpool_postgres::Manager::new' crates/knowledge-store-postgres-lib/src/store.rs
|
||||
grep -F 'deadpool_postgres::Pool::builder' crates/knowledge-store-postgres-lib/src/store.rs
|
||||
grep -F 'tokio_postgres::NoTls' crates/knowledge-store-postgres-lib/src/store.rs
|
||||
grep -F 'run_async(client)' crates/knowledge-store-postgres-lib/src/store.rs
|
||||
```
|
||||
|
||||
### Migration et test ciblé
|
||||
|
||||
```bash
|
||||
grep -F 'CREATE TABLE knowledge_sources' crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
grep -F "CHECK (family IN ('pgr', 'howto', 'snippet', 'project_local'))" crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
grep -F 'CREATE TABLE knowledge_source_sync_state' crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
grep -F 'REFERENCES knowledge_sources(source_id) ON DELETE CASCADE' crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
grep -F 'last_synchronized_revision TEXT NOT NULL' crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
|
||||
! grep -E -i '\b(content|body|credential|account|proposal|job)\b' crates/knowledge-store-postgres-lib/migrations/V1__initial_sources.sql
|
||||
|
||||
grep -F '#[ignore = "requires KNOWLEDGE_POSTGRES_TEST_URL"]' crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
grep -F 'CREATE SCHEMA' crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
grep -F 'DROP SCHEMA' crates/knowledge-store-postgres-lib/tests/postgres_migration.rs
|
||||
```
|
||||
|
||||
### Markdown puis séquence Rust
|
||||
|
||||
```bash
|
||||
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.4-alpha.2-metadata.json
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test -p knowledge-store-postgres-lib
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
### Composition workspace
|
||||
|
||||
```bash
|
||||
python3 -c 'import json; from pathlib import Path; m=json.loads(Path("/tmp/sasedev-knowledge-0.0.4-alpha.2-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","knowledge-store-postgres-lib"}; assert len(m["workspace_members"]) == 10; assert {p["name"] for p in m["packages"]} == expected; print("Knowledge 0.0.4-alpha.2 workspace audit: clean")'
|
||||
```
|
||||
|
||||
### PostgreSQL réel obligatoire
|
||||
|
||||
```bash
|
||||
test -n "${KNOWLEDGE_POSTGRES_TEST_URL:-}"
|
||||
cargo test -p knowledge-store-postgres-lib --test postgres_migration -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Si la première commande échoue ou si le test ne peut pas joindre PostgreSQL, arrêter ici et reporter `BLOCKED` avec le transcript obtenu.
|
||||
|
||||
### Hygiène, lockfiles et staging
|
||||
|
||||
```bash
|
||||
python3 -c 'from pathlib import Path; roots=[Path("README.md"),Path("RULES.md"),Path("ROADMAP.md"),Path("CHANGELOG.md"),Path("Cargo.toml"),Path("VERSION"),Path("docs"),Path("prompts"),Path("deltas"),Path("history"),Path("crates")]; allowed={".md",".rs",".toml",".sql"}; paths=[p for r in roots for p in ([r] if r.is_file() else r.rglob("*")) if p.is_file() and (p.name == "VERSION" or p.suffix in allowed)]; 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())]; assert not bad, bad; print(f"Knowledge 0.0.4-alpha.2 text hygiene: clean ({len(paths)} file(s))")'
|
||||
|
||||
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 diff --quiet -- Web/knowledge-template-lab/package-lock.json
|
||||
|
||||
git add -A
|
||||
git diff --cached --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
## Transition
|
||||
|
||||
### PASS
|
||||
|
||||
Produire séparément :
|
||||
|
||||
```text
|
||||
history/0.0.4/alpha.2.md
|
||||
```
|
||||
|
||||
avec les résultats réellement observés. Après application du history, commit/push `0.0.4-alpha.2`, puis préparer `0.0.4-alpha.3` pour la persistance minimale de source et d'état de synchronisation.
|
||||
|
||||
### FAIL
|
||||
|
||||
Produire le history réel et le committer avant correction. Une erreur bornée de code, manifest, migration ou test utilise `0.0.4-alpha.2.fix.1`.
|
||||
|
||||
### BLOCKED
|
||||
|
||||
Si seul le PostgreSQL réel requis est indisponible, historiser `BLOCKED` sans prétendre que la gate a passé. Ne modifier ni le code ni l'architecture tant qu'aucun défaut n'est démontré ; reprendre la même preuve dès qu'un PostgreSQL de test est disponible.
|
||||
|
||||
### REPLAN
|
||||
|
||||
Replanifier si la toolchain stable réellement utilisée est incompatible avec les versions retenues, si l'isolation par schéma n'est pas fiable ou si l'ownership PostgreSQL doit être changé.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/000-README.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# Documentation SaseDev Knowledge
|
||||
|
||||
@@ -11,16 +11,36 @@
|
||||
|
||||
- [`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.
|
||||
- [`studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md`](studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md) — comparaison PostgreSQL de démarrage `0.0.4`, décision `tokio-postgres`/Deadpool/Refinery, ownership, schéma minimal, migrations et tests éphémères.
|
||||
|
||||
## 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é.
|
||||
- [`plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md`](plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md) — plan actif de la fondation PostgreSQL partagée `0.0.4`.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -30,4 +50,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.
|
||||
|
||||
@@ -1,61 +1,80 @@
|
||||
<!-- file: docs/architecture/001-PROJECT_PROFILE.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Profil du projet Knowledge
|
||||
|
||||
## Baseline autoritaire
|
||||
|
||||
Le cadrage initial repose sur :
|
||||
La stable produit depuis laquelle `0.0.4` 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.3
|
||||
Knowledge stable commit b3823972449150b72905930b806d4d15471cddce
|
||||
Knowledge archive SHA-256 82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d
|
||||
Knowledge publish report sasedev-knowledge-0.0.3-publish.001-report.md = PASS
|
||||
Knowledge start prompt prompts/004-V0_0_4_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.
|
||||
L'archive taggée `v0.0.3` a été vérifiée avant le cadrage `0.0.4-alpha.1` : `VERSION` et le workspace Cargo valent `0.0.3`, les douze deltas et douze histories `0.0.3` sont appariés, `rel.1.fix.1 = PASS` clôt la release et le commentaire ZIP contient le commit stable ci-dessus.
|
||||
|
||||
Cette constatation de prévalidation ne remplace aucune gate opérateur Knowledge.
|
||||
La fondation `0.0.1` a été construite sous PGR `v0.1.1`. Cette identité reste historique dans les artefacts `0.0.1`.
|
||||
|
||||
## Nature de l'instanciation
|
||||
Le démarrage explicite de `0.0.2` a réévalué PGR et a retenu la stable suivante comme source d'adoption :
|
||||
|
||||
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.
|
||||
```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 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 :
|
||||
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.
|
||||
|
||||
`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 |
|
||||
@@ -63,96 +82,124 @@ Les profils Java/Maven/Gradle, Android et autres technologies absentes ne sont p
|
||||
| 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/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é |
|
||||
| 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 ; `0.0.4` matérialise sa première fondation sans déplacer la vérité éditoriale de Git |
|
||||
| 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` |
|
||||
| Toolkit PostgreSQL | `KEEP-LOCAL` | `tokio-postgres` + `deadpool-postgres`, async/Tokio et SQL explicite ; aucun ORM sélectionné |
|
||||
| Migrations PostgreSQL | `KEEP-LOCAL` | SQL versionné possédé par `knowledge-store-postgres-lib`, appliqué explicitement via Refinery |
|
||||
| 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 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.
|
||||
| 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Les responsabilités matérialisées sont :
|
||||
|
||||
```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-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
|
||||
knowledge-store-postgres-lib pool, migrations et état applicatif PostgreSQL partagé
|
||||
```
|
||||
|
||||
Les responsabilités suivantes restent candidates et ne deviennent des packages que lorsqu'elles possèdent un ownership réel :
|
||||
`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.
|
||||
|
||||
`0.0.4-alpha.2` matérialise `knowledge-store-postgres-lib` comme responsabilité réelle : configuration explicite, pool `deadpool-postgres`, migrations Refinery et premier schéma partagé. Aucun `knowledge-store-api` symétrique n'est introduit.
|
||||
|
||||
Les responsabilités suivantes restent candidates et non matérialisées :
|
||||
|
||||
```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.
|
||||
|
||||
## Ouverture `0.0.4` — fondation PostgreSQL partagée
|
||||
|
||||
Après publication vérifiée de `v0.0.3`, `0.0.4-alpha.1` consomme les `DEFER` portant sur le toolkit PostgreSQL et les migrations.
|
||||
|
||||
La décision locale est :
|
||||
|
||||
```text
|
||||
owner knowledge-store-postgres-lib
|
||||
driver tokio-postgres 0.7.18
|
||||
modèle SQL explicite, sans ORM
|
||||
pool deadpool-postgres 0.14.2
|
||||
migrations SQL versionné + Refinery 0.9.2
|
||||
premier état source logique + dernière révision synchronisée
|
||||
autorité contenu Git
|
||||
```
|
||||
|
||||
`alpha.2` matérialise cette décision avec une façade crate-root limitée à `PostgresStoreConfig`, `PostgresStore` et `PostgresStoreError`, plus `V1__initial_sources.sql`. La configuration est injectée ; la crate ne lit pas `KNOWLEDGE_POSTGRES_TEST_URL` ni une autre variable d'environnement. La première connexion utilise `NoTls` et un backend TLS reste différé tant qu'un déploiement n'en démontre pas le besoin.
|
||||
|
||||
La configuration physique Git reste possédée par `knowledge-git-lib`. Le premier schéma PostgreSQL ne stocke pas de credentials Git et ne transforme pas les URLs/paths physiques en vérité métier.
|
||||
|
||||
Les `DEFER` Gitea/forge, scheduler, recherche avancée, auth/capabilities, formats finaux de contenu, quatrième application admin, partage multi-processus de racine Git, LFS/submodules et rétention avancée restent inchangés. L'horizon `0.0.5` à `0.0.10` reste cohérent et n'est pas replanifié par cette étude.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/002-KNOWLEDGE_ARCHITECTURE.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Architecture de fondation Knowledge
|
||||
|
||||
@@ -15,25 +15,29 @@ 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 :
|
||||
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
|
||||
```
|
||||
|
||||
`alpha.5` complète la fondation avec :
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
`0.0.4-alpha.2` matérialise `knowledge-store-postgres-lib` comme owner concret du pool, des migrations et de l'état PostgreSQL partagé. La crate masque `tokio-postgres`, Deadpool et Refinery derrière sa façade publique et aucun `knowledge-store-api` symétrique n'est introduit sans besoin de substitution démontré.
|
||||
|
||||
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.
|
||||
|
||||
Le workspace est une application déployable à train de release unique. Knowledge choisit néanmoins de conserver `Cargo.lock` comme état généré local et ignoré par Git ; les manifests Cargo portent le contrat versionné des dépendances. Les artefacts Cargo sont dirigés hors du dépôt vers `../builds/sasedev-knowledge/target/`.
|
||||
@@ -69,9 +73,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 +109,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 Git. Depuis `0.0.2-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 +221,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 +233,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.
|
||||
|
||||
Le cas d'usage de synchronisation existant continue de recevoir `previous_revision` de l'appelant et retourne explicitement la révision observée ainsi qu'un résultat `Changed` ou `Unchanged`. `0.0.4-alpha.2` matérialise seulement le store et le schéma ; le raccord de cette persistance au cas d'usage reste réservé à `alpha.4`, 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
|
||||
|
||||
@@ -225,7 +302,30 @@ La baseline retient **une base PostgreSQL logique partagée** par les trois appl
|
||||
|
||||
Ce choix est justifié par l'état transversal : identités, sessions, propositions, synchronisation, relations entre familles, index et recherche. Il évite trois bases divergentes pour des concepts qui appartiennent à la même plateforme.
|
||||
|
||||
PostgreSQL MAY contenir des documents/index/métadonnées dérivés et assurer une première capacité de recherche. Aucun moteur de recherche externe n'est sélectionné en `0.0.1`.
|
||||
### Fondation matérialisée pour `0.0.4`
|
||||
|
||||
`0.0.4-alpha.1` sélectionne le toolkit ; `0.0.4-alpha.2` matérialise `knowledge-store-postgres-lib` avec `tokio-postgres 0.7.18`, `deadpool-postgres 0.14.2` et Refinery `0.9.2`. La façade publique reste limitée à `PostgresStoreConfig`, `PostgresStore` et `PostgresStoreError`. Les types du driver, du pool et du migrator ne remontent ni dans le domaine ni dans les contrats applicatifs, et aucun `knowledge-store-api` symétrique n'est introduit sans seconde implémentation démontrée.
|
||||
|
||||
La configuration est injectée par la composition : la crate reçoit une URL PostgreSQL et une taille maximale de pool. Elle ne lit pas directement les variables d'environnement du processus, n'expose pas l'URL par son API publique et ne journalise jamais l'URL ou les credentials. Une configuration invalide, une acquisition de pool ou une migration impossible produit une erreur récupérable ; aucun fallback fichier/mémoire ne masque l'indisponibilité PostgreSQL. La première implémentation utilise `tokio_postgres::NoTls`; un backend TLS ne sera ajouté que lorsqu'une cible de déploiement le justifiera.
|
||||
|
||||
Le premier schéma utile est volontairement borné :
|
||||
|
||||
```text
|
||||
knowledge_sources
|
||||
source_id TEXT PRIMARY KEY
|
||||
family TEXT NOT NULL CHECK (pgr/howto/snippet/project_local)
|
||||
git_reference TEXT NOT NULL
|
||||
|
||||
knowledge_source_sync_state
|
||||
source_id TEXT PRIMARY KEY REFERENCES knowledge_sources(source_id) ON DELETE CASCADE
|
||||
last_synchronized_revision TEXT NOT NULL
|
||||
```
|
||||
|
||||
`git_reference` est la référence Git logique à suivre ; la localisation physique locale/remote et les credentials restent hors de cette table et sous les frontières de configuration appropriées. La dernière révision synchronisée est un état applicatif, pas une nouvelle autorité de contenu : elle peut être reconstruite/réconciliée à partir de Git et une valeur obsolète provoque au pire une resynchronisation.
|
||||
|
||||
La migration `V1__initial_sources.sql` est embarquée au build et appliquée uniquement via `PostgresStore::apply_migrations`; Refinery conserve son historique dans PostgreSQL. Le test d'intégration réel reste `#[ignore]` pour ne pas transformer tout `cargo test` en dépendance externe implicite. La gate PostgreSQL vérifie explicitement `KNOWLEDGE_POSTGRES_TEST_URL`, crée un schéma unique, borne le `search_path` de la connexion du store, exécute la migration, vérifie `knowledge_sources`, `knowledge_source_sync_state` et `refinery_schema_history`, puis supprime le schéma. L'absence du service requis rend la gate `BLOCKED`.
|
||||
|
||||
PostgreSQL MAY contenir ultérieurement des documents/index/métadonnées dérivés et assurer une première capacité de recherche. Le corps publié et ses métadonnées éditoriales autoritaires restent dans Git. `0.0.4` ne matérialise pas encore le parsing/projection final prévu par l'horizon `0.0.5`.
|
||||
|
||||
Les enregistrements dérivés doivent porter au minimum l'identité de famille/source et la révision Git afin de savoir exactement quel contenu ils représentent.
|
||||
|
||||
@@ -275,9 +375,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 +391,55 @@ 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.
|
||||
|
||||
## Ouverture `0.0.4` — persistance partagée
|
||||
|
||||
La stable `v0.0.3` ayant fermé le chemin Git générique, `0.0.4` matérialise la première persistance partagée nécessaire aux prochains cas d'usage sans déplacer l'autorité éditoriale.
|
||||
|
||||
La trajectoire retenue est :
|
||||
|
||||
```text
|
||||
alpha.1 étude, ownership, schéma, migrations et tests
|
||||
alpha.2 crate PostgreSQL + pool + première migration
|
||||
alpha.3 persistance source logique + état de synchronisation
|
||||
alpha.4 raccord applicatif de la révision précédente persistée
|
||||
beta réconciliation + horizon
|
||||
rc/rel stabilisation et publication
|
||||
```
|
||||
|
||||
Le raccord applicatif futur doit conserver la primitive `synchronize_repository` provider-neutral : la persistance fournit la révision précédente et reçoit la révision observée, mais ne devient ni un backend Git ni une condition pour interpréter l'identité du commit.
|
||||
|
||||
## 10. Administration
|
||||
|
||||
@@ -350,9 +498,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 +520,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.
|
||||
|
||||
136
docs/architecture/003-PGR_ADOPTION_MAP.md
Normal file
136
docs/architecture/003-PGR_ADOPTION_MAP.md
Normal 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.
|
||||
324
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
Normal file
324
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
Normal 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.
|
||||
301
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
Normal file
301
docs/plans/003-V0_0_3_GIT_WRITE_PLAN.md
Normal 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.
|
||||
231
docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
Normal file
231
docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md
Normal file
@@ -0,0 +1,231 @@
|
||||
<!-- file: docs/plans/004-V0_0_4_POSTGRES_FOUNDATION_PLAN.md -->
|
||||
<!-- version: 3 -->
|
||||
|
||||
# Plan 0.0.4 — fondation PostgreSQL partagée
|
||||
|
||||
## Statut et sources
|
||||
|
||||
Release ouverte depuis la stable autoritaire :
|
||||
|
||||
```text
|
||||
Knowledge tag v0.0.3
|
||||
Knowledge commit b3823972449150b72905930b806d4d15471cddce
|
||||
Knowledge SHA-256 82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d
|
||||
publish report sasedev-knowledge-0.0.3-publish.001-report.md = PASS
|
||||
start prompt prompts/004-V0_0_4_START_PROMPT.md
|
||||
```
|
||||
|
||||
Le plan est actif à partir de `0.0.4-alpha.1`. PGR `v0.1.3` reste une provenance historique et aucune adoption PGR nouvelle n'est engagée.
|
||||
|
||||
## Mission
|
||||
|
||||
Matérialiser la première fondation PostgreSQL partagée de Knowledge afin de conserver un état transversal minimal pour les sources et la synchronisation, avec migrations reproductibles et tests PostgreSQL réels, sans déplacer la vérité éditoriale hors de Git.
|
||||
|
||||
À la clôture de `0.0.4`, le workspace doit posséder un owner PostgreSQL concret et partagé, un premier schéma versionné, un pool/configuration injectables et un chemin persistant pour la dernière révision synchronisée utilisable par l'orchestration applicative.
|
||||
|
||||
## État acquis et questions ouvertes
|
||||
|
||||
Acquis depuis `0.0.3` :
|
||||
|
||||
- Git générique de lecture et snapshot ;
|
||||
- écriture Git contrôlée et push gardé ;
|
||||
- `knowledge-app-lib` pour la synchronisation provider-neutral ;
|
||||
- trois launchers Web et composition Maud/Vite ;
|
||||
- base PostgreSQL logique partagée déjà retenue par l'architecture ;
|
||||
- horizon produit `0.0.5` à `0.0.10` révisable.
|
||||
|
||||
Décisions de `alpha.1` :
|
||||
|
||||
- `tokio-postgres` sélectionné comme driver, `deadpool-postgres` comme pool et Refinery comme migrator, sans ORM ;
|
||||
- `knowledge-store-postgres-lib` retenu comme owner ;
|
||||
- migrations SQL versionnées possédées par la crate ;
|
||||
- premier schéma limité aux sources logiques et à la dernière révision synchronisée ;
|
||||
- test PostgreSQL via URL explicitement fournie et schéma éphémère isolé ;
|
||||
- aucune lecture implicite de secrets par la crate et aucun fallback de stockage.
|
||||
|
||||
Décisions matérialisées par `alpha.2` :
|
||||
|
||||
- façade publique minimale `PostgresStoreConfig` / `PostgresStore` / `PostgresStoreError` ;
|
||||
- pool borné par `max_pool_size` sans exposition de types Deadpool ;
|
||||
- versions revalidées `tokio-postgres 0.7.18`, `deadpool-postgres 0.14.2`, Refinery `0.9.2` et Tokio test `1.53.1`, avec features minimales `runtime`, `rt_tokio_1` et `tokio-postgres` respectivement ;
|
||||
- migration `V1__initial_sources.sql` embarquée et appliquée explicitement ;
|
||||
- premier backend sans TLS via `NoTls`, sans dépendance TLS prématurée ;
|
||||
- test réel de migration isolé par schéma et déclenché explicitement par la gate.
|
||||
|
||||
Questions laissées aux tranches suivantes :
|
||||
|
||||
- besoin réel d'un contrat passif de persistance dans `knowledge-app-lib` pour le raccord `alpha.4` ;
|
||||
- backend TLS à sélectionner uniquement si le déploiement visé l'exige ;
|
||||
- paramètres supplémentaires de pool uniquement sur besoin runtime observé.
|
||||
|
||||
## Scope
|
||||
|
||||
`0.0.4` couvre :
|
||||
|
||||
- une crate PostgreSQL partagée réelle ;
|
||||
- `tokio-postgres` PostgreSQL/Tokio avec pool `deadpool-postgres` ;
|
||||
- première migration reproductible ;
|
||||
- persistance de `SourceId`, `ContentFamily`, référence Git logique et dernière révision synchronisée ;
|
||||
- erreurs récupérables sans fuite de credentials ;
|
||||
- tests unitaires ciblés et intégration PostgreSQL réelle ;
|
||||
- raccord applicatif minimal permettant d'utiliser l'état persistant sans modifier le contrat Git ;
|
||||
- documentation durable, beta, RC et fermeture stable.
|
||||
|
||||
## Hors scope
|
||||
|
||||
Restent hors `0.0.4` :
|
||||
|
||||
- corps/métadonnées éditoriales publiées en PostgreSQL comme vérité ;
|
||||
- modèles de contenu finaux et parsing complet ;
|
||||
- projections finales/indexation détaillée ;
|
||||
- recherche avancée ;
|
||||
- Gitea/forge et webhooks spécifiques ;
|
||||
- scheduler/jobs ;
|
||||
- auth/session/rôles/capabilities ;
|
||||
- propositions/revues/publication produit ;
|
||||
- templates publics/admin et routes finales ;
|
||||
- quatrième application admin ;
|
||||
- rebase/merge automatique ;
|
||||
- partage multi-processus de la racine Git ;
|
||||
- LFS/submodules et rétention avancée.
|
||||
|
||||
## Stratégie de validation
|
||||
|
||||
La validation reste proportionnée à chaque tranche.
|
||||
|
||||
Les invariants documentaires et Rust continuent d'utiliser :
|
||||
|
||||
```text
|
||||
Markdown + hygiène texte
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
audits Rust locaux
|
||||
cargo metadata / cargo check
|
||||
Clippy strict
|
||||
tests ciblés puis workspace lorsque le contrat devient transverse
|
||||
git diff --check / git diff --cached --check
|
||||
```
|
||||
|
||||
À partir de la première migration, une gate PostgreSQL réelle devient obligatoire. Elle utilise `KNOWLEDGE_POSTGRES_TEST_URL`, isole chaque exécution dans un schéma temporaire et nettoie cet état après test. Si la tranche requiert cette preuve et qu'aucun PostgreSQL n'est disponible, le statut est `BLOCKED`.
|
||||
|
||||
Les gates Web/npm ne sont pas rejouées pour les alphas sans diff `Web/` ou contrat Web. Les phases de stabilisation peuvent imposer une non-régression runtime globale.
|
||||
|
||||
## Trajectoire prévisionnelle
|
||||
|
||||
### alpha.1 — baseline, étude et cadrage
|
||||
|
||||
- vérifier intégralement `v0.0.3` et `publish.001` ;
|
||||
- relire owners, architecture, deltas/histories, `DEFER` et horizon ;
|
||||
- confirmer l'autonomie normative Knowledge ;
|
||||
- comparer les options PostgreSQL actuelles ;
|
||||
- décider le driver `tokio-postgres`, le pool `deadpool-postgres`, Refinery, l'owner, le schéma, la configuration et la stratégie de test ;
|
||||
- créer l'étude `008`, le présent plan et le delta ;
|
||||
- ne modifier aucun code produit, dépendance ou migration ;
|
||||
- autoriser `alpha.2` seulement après `PASS` opérateur.
|
||||
|
||||
### alpha.2 — owner PostgreSQL, pool et première migration
|
||||
|
||||
- créer `knowledge-store-postgres-lib` avec façade crate-root conforme ;
|
||||
- ajouter uniquement les dépendances/features nécessaires de `tokio-postgres`, `deadpool-postgres`, Refinery et Tokio ;
|
||||
- matérialiser la configuration explicite de connexion/pool et les erreurs publiques sans fuite de secret ;
|
||||
- ajouter `migrations/` sous la crate et la migration initiale `knowledge_sources` + `knowledge_source_sync_state` ;
|
||||
- exposer une opération explicite d'application des migrations ;
|
||||
- prouver la migration sur PostgreSQL réel avec schéma de test isolé ;
|
||||
- ne raccorder encore aucun launcher si aucun use case runtime ne l'exige.
|
||||
|
||||
### alpha.2.fix.1 — documentation de la crate de test PostgreSQL
|
||||
|
||||
- conserver `alpha.2` historisée `FAIL` à cause de Clippy strict ;
|
||||
- ajouter uniquement la documentation de crate manquante dans `tests/postgres_migration.rs` ;
|
||||
- ne modifier ni le code produit, ni le schéma, ni les dépendances, ni la façade publique ;
|
||||
- rejouer Clippy strict, les tests Rust et la preuve PostgreSQL réelle ;
|
||||
- autoriser `alpha.3` uniquement après `PASS` complet du fix.
|
||||
|
||||
### alpha.3 — persistance de source et état de synchronisation
|
||||
|
||||
- matérialiser les opérations minimales de création/mise à jour/lecture de configuration logique ;
|
||||
- convertir `ContentFamily`, `SourceId`, référence logique et `GitRevision` sans faire remonter les types PostgreSQL/Deadpool/Refinery au domaine ;
|
||||
- lire et enregistrer `last_synchronized_revision` ;
|
||||
- tester insertion, mise à jour, absence d'état, contraintes et isolation des sources sur PostgreSQL réel ;
|
||||
- prouver qu'aucun corps de contenu ou credential Git n'est persisté par ce schéma.
|
||||
|
||||
### alpha.4 — raccord applicatif persistant
|
||||
|
||||
- conserver `synchronize_repository` comme primitive provider-neutral ;
|
||||
- ajouter seulement le contrat applicatif nécessaire pour charger/enregistrer la révision précédente sans dépendance au driver/pool/migrator dans `knowledge-app-lib` ;
|
||||
- implémenter ce contrat côté `knowledge-store-postgres-lib` ;
|
||||
- composer un cas d'usage persistant qui charge l'état, synchronise via Git et enregistre la révision uniquement après succès ;
|
||||
- tester Changed/Unchanged, première synchronisation et absence d'écriture après erreur Git/store ;
|
||||
- ne pas ajouter scheduler, projection de contenu ou route Web.
|
||||
|
||||
### beta.1 — réconciliation et revue d'horizon
|
||||
|
||||
- réconcilier le schéma, l'API, le lifecycle et les tests réellement obtenus ;
|
||||
- vérifier que Git reste l'autorité du contenu et que le state PostgreSQL est reconstructible ;
|
||||
- préparer `CHANGELOG.md` en `0.0.4 (candidate)` seulement à ce stade ;
|
||||
- réévaluer `0.0.5` modèles/projections et au moins `0.0.6` / `0.0.7` ;
|
||||
- préparer `prompts/005-V0_0_5_START_PROMPT.md` uniquement si la prochaine mission reste suffisamment décidée ;
|
||||
- rejouer les preuves PostgreSQL et non-régression workspace nécessaires à la stabilisation.
|
||||
|
||||
### rc.1 — candidate stable
|
||||
|
||||
- geler le scope ;
|
||||
- rejouer la gate complète Rust/PostgreSQL applicable ;
|
||||
- vérifier migrations depuis un schéma vide, persistance source/state et raccord applicatif ;
|
||||
- vérifier deltas/histories, hors-scope et hygiène de livraison ;
|
||||
- rejouer la non-régression Web/runtime exigée par la phase de stabilisation ;
|
||||
- n'introduire aucune fonctionnalité nouvelle.
|
||||
|
||||
### rel.1 — promotion stable pré-tag
|
||||
|
||||
- promouvoir mécaniquement `VERSION` et workspace vers `0.0.4` ;
|
||||
- finaliser changelog, README, roadmap et plan depuis le résultat réellement acquis ;
|
||||
- vérifier le handoff `0.0.5` seulement s'il a été autorisé par la revue beta ;
|
||||
- rejouer la gate stable complète ;
|
||||
- produire le history observé avant commit stable/tag ;
|
||||
- ne fabriquer aucun rapport post-tag avant publication réelle.
|
||||
|
||||
## Déclencheurs de REPLAN
|
||||
|
||||
REPLAN avant de poursuivre si :
|
||||
|
||||
- les versions retenues de `tokio-postgres`, `deadpool-postgres` ou Refinery sont incompatibles avec la toolchain stable réellement utilisée ou les lints locaux ;
|
||||
- le premier schéma exige déjà de stocker le corps éditorial publié pour fonctionner ;
|
||||
- une seconde implémentation de store rend une frontière `-api` réellement nécessaire ;
|
||||
- le raccord applicatif crée une dépendance cyclique ou oblige le domaine à dépendre du driver, du pool ou du migrator ;
|
||||
- la stratégie de test par schéma isolé ne peut pas prouver les migrations de manière fiable ;
|
||||
- une connexion PostgreSQL devient une dépendance obligatoire de tous les launchers sans cas d'usage qui la justifie ;
|
||||
- un scheduler, worker ou service réseau supplémentaire devient nécessaire pour fermer la mission ;
|
||||
- une tranche atteint `.fix.3`.
|
||||
|
||||
La conclusion du REPLAN utilise `CONTINUE`, `SPLIT`, `CONTINUE-NEXT-SESSION` ou `ABANDON`.
|
||||
|
||||
## Clôture et transition
|
||||
|
||||
`0.0.4` est fermable lorsque :
|
||||
|
||||
- `knowledge-store-postgres-lib` est un owner réel et testé ;
|
||||
- une base PostgreSQL vide peut être migrée de manière reproductible ;
|
||||
- les sources logiques et leur dernière révision synchronisée sont persistées ;
|
||||
- le pool et les erreurs ne fuient aucun secret ;
|
||||
- l'indisponibilité PostgreSQL n'est jamais masquée par un fallback ;
|
||||
- l'orchestration peut utiliser la révision persistée sans coupler le domaine à SQL ;
|
||||
- Git reste l'autorité de toute donnée éditoriale publiée ;
|
||||
- beta/RC ont fermé les non-régressions et l'horizon.
|
||||
|
||||
À l'ouverture, seule `alpha.1` est matérialisée. Aucun history `0.0.4` n'existe avant la gate opérateur de cette tranche.
|
||||
|
||||
## Revue d'horizon
|
||||
|
||||
L'étude `alpha.1` ne modifie pas l'horizon issu de `0.0.3-beta.1` :
|
||||
|
||||
```text
|
||||
0.0.5 modèles de contenu et projections PGR/HOWTO/snippet
|
||||
0.0.6 templates publics des trois sites
|
||||
0.0.7 templates d'administration des trois sites
|
||||
0.0.8 lecture publique Actix Web / Maud
|
||||
0.0.9 administration Actix Web / Maud et autorisation
|
||||
0.0.10 gestion, proposition, revue et publication
|
||||
```
|
||||
|
||||
La revue beta de `0.0.4` devra confirmer ou replanifier au minimum `0.0.5`, `0.0.6` et `0.0.7` à partir de la persistance réellement obtenue.
|
||||
25
docs/rules/000-README.md
Normal file
25
docs/rules/000-README.md
Normal 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.
|
||||
122
docs/rules/200-GOVERNANCE.md
Normal file
122
docs/rules/200-GOVERNANCE.md
Normal 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
127
docs/rules/210-WORKFLOW.md
Normal 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.
|
||||
95
docs/rules/300-DOCUMENTATION.md
Normal file
95
docs/rules/300-DOCUMENTATION.md
Normal 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
|
||||
```
|
||||
123
docs/rules/400-VALIDATION.md
Normal file
123
docs/rules/400-VALIDATION.md
Normal 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.
|
||||
81
docs/rules/510-RUST_CARGO.md
Normal file
81
docs/rules/510-RUST_CARGO.md
Normal 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.
|
||||
57
docs/rules/530-WEB_FRONTEND.md
Normal file
57
docs/rules/530-WEB_FRONTEND.md
Normal 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.
|
||||
245
docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
Normal file
245
docs/studies/004-V0_0_2_GIT_IMPLEMENTATION_STUDY.md
Normal 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.
|
||||
100
docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md
Normal file
100
docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md
Normal 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.
|
||||
217
docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md
Normal file
217
docs/studies/006-V0_0_3_GIT_WRITE_STUDY.md
Normal 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.
|
||||
178
docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
Normal file
178
docs/studies/007-V0_0_3_GIT_WRITE_RECONCILIATION.md
Normal 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.
|
||||
255
docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
Normal file
255
docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md
Normal file
@@ -0,0 +1,255 @@
|
||||
<!-- file: docs/studies/008-V0_0_4_POSTGRES_FOUNDATION_STUDY.md -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Étude 0.0.4 — fondation PostgreSQL partagée
|
||||
|
||||
## Objet
|
||||
|
||||
Cette étude de `0.0.4-alpha.1` décide l'ownership et la première architecture PostgreSQL de SaseDev Knowledge avant toute implémentation de stockage.
|
||||
|
||||
Elle part exclusivement de la stable Knowledge `v0.0.3` publiée et vérifiée. Elle ne consulte ni n'adopte une nouvelle version de PGR.
|
||||
|
||||
## Baseline autoritaire vérifiée
|
||||
|
||||
La baseline reçue au démarrage est :
|
||||
|
||||
```text
|
||||
tag v0.0.3
|
||||
commit b3823972449150b72905930b806d4d15471cddce
|
||||
archive sasedev-knowledge-v0.0.3.zip
|
||||
SHA-256 82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d
|
||||
report sasedev-knowledge-0.0.3-publish.001-report.md = PASS
|
||||
```
|
||||
|
||||
La vérification de démarrage confirme :
|
||||
|
||||
- `VERSION = 0.0.3` et `[workspace.package].version = 0.0.3` dans l'archive ;
|
||||
- neuf packages Cargo et aucune crate PostgreSQL déjà matérialisée ;
|
||||
- douze deltas et douze histories `0.0.3`, appariés jusqu'à `rel.1.fix.1 = PASS` ;
|
||||
- Git lecture/écriture contrôlée stable et provider-neutral ;
|
||||
- `ROADMAP.md` engage déjà `0.0.4` comme fondation PostgreSQL partagée ;
|
||||
- `prompts/004-V0_0_4_START_PROMPT.md` est présent dans la stable ;
|
||||
- PGR `v0.1.3` reste uniquement une provenance historique de l'adoption fermée en `0.0.2`.
|
||||
|
||||
## Contraintes acquises
|
||||
|
||||
La fondation doit préserver simultanément :
|
||||
|
||||
```text
|
||||
Git vérité du contenu publié
|
||||
PostgreSQL état applicatif transversal et projections reconstructibles
|
||||
knowledge-domain-lib aucun type SQL/Actix/Gitea
|
||||
knowledge-git-api contrats Git provider-neutral
|
||||
knowledge-git-lib configuration physique et opérations Git
|
||||
knowledge-app-lib orchestration applicative
|
||||
knowledge-web-lib composition Web partagée
|
||||
```
|
||||
|
||||
La première implémentation PostgreSQL ne doit pas entraîner par symétrie une nouvelle crate `-api`, un service réseau, trois bases séparées, un ORM complet, un scheduler ou un modèle de contenu final.
|
||||
|
||||
## Sources techniques consultées
|
||||
|
||||
Consultation effectuée le `2026-09-30` sur les documentations publiées des crates comparées :
|
||||
|
||||
- SQLx `0.9.0` et ses features/MSRV : <https://docs.rs/crate/sqlx/0.9.0/source/Cargo.toml.orig> ;
|
||||
- `PgPool` SQLx : <https://docs.rs/sqlx/0.9.0/sqlx/postgres/type.PgPool.html> ;
|
||||
- migrations et `migrate!` SQLx : <https://docs.rs/sqlx/0.9.0/sqlx/migrate/index.html> et <https://docs.rs/sqlx/0.9.0/sqlx/macro.migrate.html> ;
|
||||
- `tokio-postgres` `0.7.18` : <https://docs.rs/tokio-postgres/0.7.18/tokio_postgres/> ;
|
||||
- `deadpool-postgres` `0.14.2` : <https://docs.rs/deadpool-postgres/0.14.2/deadpool_postgres/> ;
|
||||
- Diesel `2.3.13` : <https://docs.rs/crate/diesel/2.3.13> ;
|
||||
- `diesel-async` `0.9.2` : <https://docs.rs/crate/diesel-async/0.9.2> ;
|
||||
- SeaORM `2.0.4` : <https://docs.rs/crate/sea-orm/2.0.4> ;
|
||||
- Refinery `0.9.2` : <https://docs.rs/crate/refinery/0.9.2>.
|
||||
|
||||
## Comparaison des options Rust
|
||||
|
||||
État des versions consultées le `2026-09-30` : SQLx `0.9.0`, `tokio-postgres` `0.7.18`, `deadpool-postgres` `0.14.2`, Diesel `2.3.13`, `diesel-async` `0.9.2`, SeaORM `2.0.4` et Refinery `0.9.2`.
|
||||
|
||||
| Option | Atouts pour Knowledge | Coûts / inadéquations | Décision |
|
||||
|----------------------------------------|---------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|----------|
|
||||
| `tokio-postgres` + Deadpool + Refinery | driver PostgreSQL async natif, responsabilités séparées, SQL explicite, pool spécialisé et migrations SQL intégrées | trois briques à versionner et intégrer ; la façade Knowledge doit normaliser leurs erreurs | `SELECT` |
|
||||
| SQLx `0.9` | async/Tokio, PostgreSQL natif, pool et migrations intégrés, SQL explicite | toolkit multi-SGBD plus large que le besoin ; MSRV `1.94`; ses macros de requêtes ne sont pas nécessaires ici | `REJECT` |
|
||||
| Diesel + `diesel-async` | typage/DSL fort, écosystème migrations, pooling disponible | modèle ORM/query-builder plus large que le premier schéma et davantage de surface de génération/traits | `REJECT` |
|
||||
| SeaORM | ORM async complet, migrations, relations et modèles riches | surdimensionné avant les modèles/projections `0.0.5`; risque de faire remonter l'ORM dans les contrats | `REJECT` |
|
||||
|
||||
La sélection privilégie des responsabilités explicitement bornées plutôt qu'un toolkit généraliste. Knowledge cible PostgreSQL comme choix de produit : l'abstraction multi-SGBD de SQLx n'apporte donc pas de valeur attendue pour cette fondation. Le coût de composition de trois briques reste acceptable parce que chacune possède une responsabilité unique et vérifiable.
|
||||
|
||||
## Décision de dépendance
|
||||
|
||||
La première implémentation utilisera `tokio-postgres` comme driver PostgreSQL asynchrone natif, `deadpool-postgres` comme pool et `refinery` comme owner technique des migrations SQL. Aucun ORM n'est sélectionné et aucune abstraction multi-SGBD n'est recherchée.
|
||||
|
||||
Les versions observées pendant l'étude sont `tokio-postgres 0.7.18`, `deadpool-postgres 0.14.2` et `refinery 0.9.2`. `tokio-postgres` et `deadpool-postgres` déclarent actuellement Rust 2024 avec un MSRV `1.85`; Refinery vise les Rust stable récents. `alpha.2` a revalidé ces versions le `2026-09-30` avant leur ajout au manifest et retient les features minimales `runtime` pour `tokio-postgres`, `rt_tokio_1` pour `deadpool-postgres` et `tokio-postgres` pour Refinery. Tokio `1.53.1` est ajouté uniquement comme runtime de test d'intégration.
|
||||
|
||||
La séparation est volontaire :
|
||||
|
||||
```text
|
||||
tokio-postgres protocole, connexion, requêtes et transactions PostgreSQL
|
||||
deadpool-postgres cycle de vie et mutualisation des connexions
|
||||
refinery découverte, historique et application des migrations SQL
|
||||
```
|
||||
|
||||
La façade publique de `knowledge-store-postgres-lib` masque ces dépendances et normalise leurs erreurs dans le type d'erreur de la crate. Les types `tokio-postgres`, Deadpool ou Refinery ne doivent pas remonter dans `knowledge-domain-lib` ni dans les contrats applicatifs.
|
||||
|
||||
`alpha.2` matérialise la première connexion avec `tokio_postgres::NoTls` et n'ajoute aucune dépendance TLS prématurée. Si une cible de déploiement PostgreSQL exige TLS, une tranche ultérieure devra sélectionner explicitement l'intégration TLS minimale compatible avec `tokio-postgres` au lieu d'hériter d'un défaut implicite.
|
||||
|
||||
## Ownership
|
||||
|
||||
La responsabilité est suffisamment concrète pour justifier :
|
||||
|
||||
```text
|
||||
knowledge-store-postgres-lib
|
||||
```
|
||||
|
||||
À partir d'`alpha.2`, cette crate possède :
|
||||
|
||||
- construction et lifecycle du pool `deadpool-postgres` ;
|
||||
- migrations SQL de Knowledge ;
|
||||
- mapping SQL des types nécessaires ;
|
||||
- persistance de configuration logique de source ;
|
||||
- persistance de l'état de synchronisation ;
|
||||
- erreurs publiques récupérables de cette frontière.
|
||||
|
||||
Elle dépendra des types domaine/Git strictement nécessaires, jamais l'inverse.
|
||||
|
||||
Aucun `knowledge-store-api` n'est créé en `0.0.4-alpha.1`. Si une deuxième implémentation réelle ou un besoin de fake transverse rend plus tard une frontière passive utile, cette décision sera réévaluée à partir d'un usage concret.
|
||||
|
||||
## Connexion, pool et secrets
|
||||
|
||||
La crate de stockage ne lit pas l'environnement. La composition lui fournit une configuration contenant au minimum l'URL PostgreSQL et les paramètres de pool retenus.
|
||||
|
||||
L'URL peut contenir un secret et respecte donc les invariants suivants :
|
||||
|
||||
- aucune valeur réelle versionnée ;
|
||||
- aucune URL complète dans les erreurs publiques ou logs ;
|
||||
- aucune valeur par défaut de production codée en dur ;
|
||||
- aucune tentative de fallback vers un fichier, SQLite ou un stockage mémoire si PostgreSQL est indisponible.
|
||||
|
||||
Le pool est partagé par l'instance applicative qui compose le store. Les trois produits peuvent pointer vers la même base logique sans que chaque launcher réimplémente sa propre couche SQL.
|
||||
|
||||
## Premier schéma utile
|
||||
|
||||
Le premier schéma est volontairement plus petit que l'état futur décrit dans l'architecture.
|
||||
|
||||
### `knowledge_sources`
|
||||
|
||||
```text
|
||||
source_id TEXT PRIMARY KEY
|
||||
family TEXT NOT NULL
|
||||
git_reference TEXT NOT NULL
|
||||
```
|
||||
|
||||
`source_id` correspond à `SourceId`. `family` représente `ContentFamily` avec une contrainte SQL sur les valeurs supportées. `git_reference` est la référence Git logique à résoudre, pas une URL remote ni un credential. Les valeurs `family` persistées sont `pgr`, `howto`, `snippet` et `project_local`; la migration doit les contraindre explicitement.
|
||||
|
||||
### `knowledge_source_sync_state`
|
||||
|
||||
```text
|
||||
source_id TEXT PRIMARY KEY REFERENCES knowledge_sources(source_id) ON DELETE CASCADE
|
||||
last_synchronized_revision TEXT NOT NULL
|
||||
```
|
||||
|
||||
La révision stockée représente la dernière révision effectivement acceptée par le cas d'usage de synchronisation. Elle est un état applicatif de comparaison. Git reste capable de fournir l'identité autoritaire et une valeur PostgreSQL perdue ou obsolète conduit à une nouvelle réconciliation, pas à une perte du contenu publié.
|
||||
|
||||
Le schéma n'ajoute pas encore :
|
||||
|
||||
- corps ou métadonnées éditoriales de contenu ;
|
||||
- révision indexée séparée avant l'implémentation réelle des projections ;
|
||||
- utilisateurs/sessions ;
|
||||
- propositions/revues ;
|
||||
- jobs ;
|
||||
- audit applicatif ;
|
||||
- moteur de recherche.
|
||||
|
||||
## Migrations
|
||||
|
||||
Les migrations sont des fichiers SQL versionnés sous :
|
||||
|
||||
```text
|
||||
crates/knowledge-store-postgres-lib/migrations/
|
||||
```
|
||||
|
||||
La crate embarque le jeu de migrations au build et expose une opération explicite d'application. Le schéma n'est pas créé implicitement à chaque requête.
|
||||
|
||||
Les migrations sont forward-only pour cette fondation : une correction de schéma publiée utilise une nouvelle migration. Un rollback destructif automatique n'est pas requis pour fermer `0.0.4`.
|
||||
|
||||
## Stratégie de test PostgreSQL
|
||||
|
||||
Le test réel doit prouver PostgreSQL, pas une imitation en mémoire.
|
||||
|
||||
La stratégie retenue est :
|
||||
|
||||
```text
|
||||
KNOWLEDGE_POSTGRES_TEST_URL
|
||||
-> PostgreSQL local ou service CI explicitement fourni
|
||||
-> création d'un schéma de test unique
|
||||
-> search_path borné à ce schéma
|
||||
-> application des migrations
|
||||
-> tests CRUD / état de synchronisation
|
||||
-> suppression du schéma
|
||||
```
|
||||
|
||||
Cette stratégie évite d'imposer Docker dans la bibliothèque tout en autorisant un service container en CI. L'absence du service requis pendant une gate qui exige le test PostgreSQL est `BLOCKED`, pas `PASS`.
|
||||
|
||||
Les tests unitaires sans PostgreSQL restent utiles pour les validations de types/configuration qui n'ont pas besoin d'un serveur réel, mais ils ne remplacent pas l'intégration PostgreSQL de migration et persistance.
|
||||
|
||||
## Raccord au cas d'usage de synchronisation
|
||||
|
||||
Le cas d'usage existant `synchronize_repository` reçoit actuellement `previous_revision` de son appelant. `0.0.4` doit conserver cette primitive provider-neutral.
|
||||
|
||||
Le raccord prévu est une orchestration supplémentaire qui :
|
||||
|
||||
```text
|
||||
charge last_synchronized_revision
|
||||
-> appelle synchronize_repository
|
||||
-> enregistre la révision observée après succès
|
||||
```
|
||||
|
||||
Un contrat passif de persistance peut vivre dans `knowledge-app-lib` si `alpha.4` démontre qu'il est nécessaire pour tester cette orchestration sans coupler l'application au driver PostgreSQL. Cette possibilité ne justifie pas à elle seule une crate `knowledge-store-api`.
|
||||
|
||||
## Indisponibilité PostgreSQL
|
||||
|
||||
Les erreurs de connexion, acquisition de pool, migration ou requête sont récupérables et typées à la frontière store.
|
||||
|
||||
Il n'existe pas de mode où une écriture est annoncée persistée alors que PostgreSQL ne l'est pas. Une future façade publique pourra continuer à servir un contenu Git déjà matérialisé seulement si son cas d'usage n'exige pas le store ; cette politique appartient à la composition de la façade, pas à un fallback interne de la crate de stockage.
|
||||
|
||||
## Impact sur les trois applications
|
||||
|
||||
`0.0.4` ne crée pas trois implémentations ou trois schémas par façade. `knowledge-store-postgres-lib` est le propriétaire partagé et la base logique est commune.
|
||||
|
||||
Les launchers ne seront modifiés que lorsqu'un cas d'usage runtime exige réellement la connexion. La fondation peut donc être testée indépendamment avant d'ajouter une dépendance de démarrage PostgreSQL à `/` et `/healthz`.
|
||||
|
||||
## DEFER conservés
|
||||
|
||||
Restent explicitement hors scope de `0.0.4` :
|
||||
|
||||
- formats finaux et parsing PGR/HOWTO/snippet ;
|
||||
- projections de contenu finales et révision indexée détaillée ;
|
||||
- recherche avancée ;
|
||||
- Gitea/forge et webhooks forge-only ;
|
||||
- scheduler/jobs ;
|
||||
- auth, sessions et rôles/capabilities ;
|
||||
- propositions/revues/publication produit ;
|
||||
- templates publics/admin et routes finales ;
|
||||
- quatrième application admin ;
|
||||
- partage multi-processus de la racine Git ;
|
||||
- Git LFS/submodules et rétention avancée.
|
||||
|
||||
## Horizon
|
||||
|
||||
L'étude ne révèle aucune dépendance fausse dans la revue `0.0.3-beta.1`.
|
||||
|
||||
L'horizon reste donc :
|
||||
|
||||
```text
|
||||
0.0.5 modèles de contenu et projections
|
||||
0.0.6 templates publics
|
||||
0.0.7 templates d'administration
|
||||
0.0.8 lecture publique Actix Web / Maud
|
||||
0.0.9 administration Actix Web / Maud et autorisation
|
||||
0.0.10 gestion, proposition, revue et publication
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
`0.0.4` peut progresser sans REPLAN : l'owner PostgreSQL est démontré, `tokio-postgres`, `deadpool-postgres` et `refinery` couvrent respectivement le driver, le pool et les migrations nécessaires, le premier schéma est borné et reconstructible par rapport à Git, et une stratégie de test PostgreSQL réelle est définie sans imposer un outil de provisioning unique.
|
||||
|
||||
`alpha.1` a fermé le cadrage en `PASS`. `alpha.2` matérialise maintenant `knowledge-store-postgres-lib`, le trio `tokio-postgres` / `deadpool-postgres` / `refinery`, une configuration/pool sans fuite de secret, la migration initiale des sources et un test PostgreSQL réel isolé par schéma. Les opérations de persistance source/state restent réservées à `alpha.3`.
|
||||
43
docs/templates/001-NEW_SESSION_PROMPT.md
vendored
Normal file
43
docs/templates/001-NEW_SESSION_PROMPT.md
vendored
Normal 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
37
docs/templates/002-RELEASE_PLAN.md
vendored
Normal 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
29
docs/templates/003-OPERATOR_HANDOFF.md
vendored
Normal 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.
|
||||
37
docs/templates/004-RUST_LIBRARY_CRATE.md
vendored
Normal file
37
docs/templates/004-RUST_LIBRARY_CRATE.md
vendored
Normal 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/001-VALIDATION_MATRIX.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 43 -->
|
||||
|
||||
# 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,436 @@ 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.
|
||||
|
||||
## Gate de `0.0.4-alpha.1`
|
||||
|
||||
La première tranche de `0.0.4` est une gate de cadrage sans code produit, dépendance PostgreSQL Rust ni migration. Elle doit prouver :
|
||||
|
||||
- `VERSION = 0.0.4-alpha.1` et `[workspace.package].version` identique ;
|
||||
- baseline `v0.0.3` identifiée par commit `b3823972449150b72905930b806d4d15471cddce`, SHA-256 `82407dc2a83be878daa65692f975681428d0adc721ff249c3c9020e3a9d1f29d` et `publish.001 = PASS` ;
|
||||
- douze deltas et douze histories `0.0.3` appariés, avec `rel.1.fix.1 = PASS` ;
|
||||
- aucun chargement/adoption PGR nouveau ;
|
||||
- étude `008` et plan `004` présents ;
|
||||
- `tokio-postgres`, `deadpool-postgres`, Refinery, `knowledge-store-postgres-lib`, le schéma minimal et la stratégie de test explicitement décidés ;
|
||||
- aucun `knowledge-store-postgres-lib`, aucune migration et aucune nouvelle dépendance réellement ajoutés dans `alpha.1` ;
|
||||
- aucun diff sous `crates/` ou `Web/` et aucun changement du changelog stable `0.0.3` ;
|
||||
- roadmap/horizon `0.0.5` à `0.0.10` inchangés parce que l'étude ne révèle aucune dépendance fausse ;
|
||||
- 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` et `cargo check --workspace` propres ;
|
||||
- `Cargo.lock` local ignoré/non suivi et `package-lock.json` Web inchangé/versionné ;
|
||||
- `git diff --cached --check` propre avant commit.
|
||||
|
||||
Clippy, tests workspace, npm/Vite et smokes HTTP ne sont pas requis par cette tranche : elle ne modifie ni source Rust, ni dépendance, ni frontend, ni runtime. La gate vérifie néanmoins `cargo check --workspace` après la promotion de version du manifest.
|
||||
|
||||
Un `PASS` autorise uniquement `0.0.4-alpha.2`. Un autre statut est historisé puis conduit à un fix borné ou à un REPLAN.
|
||||
|
||||
## Gate de `0.0.4-alpha.2`
|
||||
|
||||
La deuxième tranche matérialise le premier owner PostgreSQL réel. Elle doit prouver :
|
||||
|
||||
- `VERSION = 0.0.4-alpha.2` et `[workspace.package].version` identique ;
|
||||
- `history/0.0.4/alpha.1.md` présent avec statut `PASS` et committé avant application ;
|
||||
- workspace composé exactement de dix packages, avec `knowledge-store-postgres-lib` comme seul nouveau membre ;
|
||||
- `tokio-postgres 0.7.18` avec `runtime`, `deadpool-postgres 0.14.2` avec `rt_tokio_1`, Refinery `0.9.2` avec `tokio-postgres` et Tokio test `1.53.1` ajouté uniquement à la crate PostgreSQL ;
|
||||
- façade crate-root limitée à `PostgresStoreConfig`, `PostgresStore` et `PostgresStoreError` ;
|
||||
- configuration injectée, sans lecture d'environnement ni getter public de l'URL PostgreSQL ;
|
||||
- pool `deadpool-postgres` borné par `max_pool_size`, driver `tokio-postgres` et première connexion `NoTls` ;
|
||||
- migration `V1__initial_sources.sql` embarquée via Refinery et appliquée seulement par `PostgresStore::apply_migrations` ;
|
||||
- tables `knowledge_sources` et `knowledge_source_sync_state` avec contrainte des familles et cascade de suppression de l'état de synchronisation ;
|
||||
- aucun corps de contenu, credential Git, compte, proposition, job ou projection finale ajouté au schéma ;
|
||||
- test unitaire de configuration sans PostgreSQL ;
|
||||
- test d'intégration PostgreSQL réel isolé dans un schéma unique et exécuté explicitement avec `KNOWLEDGE_POSTGRES_TEST_URL` ;
|
||||
- absence de PostgreSQL réel pendant cette gate classée `BLOCKED`, jamais masquée par un skip ;
|
||||
- aucun diff sous `Web/`, les trois launchers, `knowledge-app-lib`, `knowledge-git-api`, `knowledge-git-lib`, `knowledge-sync-api` ou `knowledge-domain-lib` ;
|
||||
- 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 sur les dix crates ;
|
||||
- `cargo metadata`, `cargo check`, Clippy strict, tests ciblés store et tests workspace propres ;
|
||||
- `Cargo.lock` local ignoré/non suivi et `package-lock.json` Web inchangé/versionné ;
|
||||
- `git diff --cached --check` propre avant commit.
|
||||
|
||||
La gate ne rejoue pas npm/Vite/HTTP : aucun fichier Web, launcher ou contrat runtime Web n'est modifié. Le test PostgreSQL marqué `#[ignore]` reste exclu des tests workspace ordinaires et est rejoué explicitement avec `--ignored` dans la gate afin que la dépendance externe soit volontaire et visible.
|
||||
|
||||
Un `PASS` autorise uniquement `0.0.4-alpha.3`. Un `FAIL` de code ou migration produit un fix borné ; une incompatibilité de toolchain/dépendances, une impossibilité d'isoler correctement le test PostgreSQL ou un besoin de modifier l'ownership déclenche un REPLAN.
|
||||
|
||||
146
history/0.0.2/alpha.1.fix.1.md
Normal file
146
history/0.0.2/alpha.1.fix.1.md
Normal 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
146
history/0.0.2/alpha.1.md
Normal 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`.
|
||||
193
history/0.0.2/alpha.2.fix.1.md
Normal file
193
history/0.0.2/alpha.2.fix.1.md
Normal 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
|
||||
```
|
||||
164
history/0.0.2/alpha.2.fix.2.md
Normal file
164
history/0.0.2/alpha.2.fix.2.md
Normal 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
160
history/0.0.2/alpha.2.md
Normal 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
|
||||
```
|
||||
173
history/0.0.2/alpha.3.fix.1.md
Normal file
173
history/0.0.2/alpha.3.fix.1.md
Normal 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
171
history/0.0.2/alpha.3.md
Normal 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
|
||||
```
|
||||
134
history/0.0.2/alpha.4.fix.1.md
Normal file
134
history/0.0.2/alpha.4.fix.1.md
Normal 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
144
history/0.0.2/alpha.4.md
Normal 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
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user