# 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 / hooks-disabled/ repositories/ .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 -- ``` Les sources remote utilisent : ```text git clone --mirror -- ``` Un mirror existant est inspecté avec : ```text git --git-dir 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", ): 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"", path assert re.fullmatch(r"", 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.