# É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` : ; - Git `clone` : ; - Git `worktree` : ; - Git `rev-parse` : ; - `gix` `0.88.0` : et ; - `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 ^{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 ` 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 / hooks-disabled/ repositories/ .git/ snapshots/ / / ``` 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= rev-parse --verify --end-of-options ^{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.