0.0.2-alpha.1
This commit is contained in:
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.
|
||||
Reference in New Issue
Block a user