From 2c52b1634fba2e922b137a3c95808af965d5defa Mon Sep 17 00:00:00 2001 From: Fusion Date: Thu, 14 May 2026 08:29:42 +0200 Subject: [PATCH] =?UTF-8?q?docs(FN-011):=20complete=20Step=208=20=E2=80=94?= =?UTF-8?q?=20update=20architecture=20and=20development=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fusion-Task-Id: FN-011 Fusion-Task-Lineage: 4a9aca6f-9d91-43aa-8d2a-d59657c1541a --- docs/architecture.md | 29 ++++++++++++++++++++++++++--- docs/development.md | 20 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 8282bc2..b5e114a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -30,17 +30,40 @@ The backend must define provider contracts before implementing any concrete adap ### 3.1 GitProvider +> **Architecture divergence note (FN-011):** The original spec defined a single +> `GitProvider(Protocol)` with `clone/fetch/push` methods. The implementation +> intentionally splits this responsibility into two abstractions: +> +> - `GitProvider` (`app/git/provider.py`) — provider API adapter for remote +> operations (`validate_connection`, `list_repositories`, `create_deploy_key`, +> etc.). +> - `GitOperations` (`app/git/operations.py`) — local Git subprocess interface +> (`clone`, `fetch`, `push`, `get_status`). +> +> This separation keeps provider-specific API logic distinct from local Git CLI +> orchestration. + ```python class GitProvider(Protocol): - def clone(self, repo_url: str, dest: Path, credentials: GitCredentials) -> None: ... - def fetch(self, repo_path: Path, credentials: GitCredentials) -> None: ... - def push(self, repo_path: Path, credentials: GitCredentials) -> None: ... + def validate_connection(self, repo_url: str, credential_id: str) -> ConnectionStatus: ... + def list_repositories(self, credential_id: str) -> list[dict[str, Any]]: ... + def create_deploy_key(self, repo_url: str, public_key: str) -> str: ... + def delete_deploy_key(self, repo_url: str, deploy_key_id: str) -> None: ... + def get_default_branch(self, repo_url: str, credential_id: str) -> str: ... ``` - Adapters: GitHub, GitLab, Gitea, Forgejo, etc. - Credentials: generated SSH keys (per-repository) or access tokens. - SSH keys must be scoped per repository connection for clean revocation. +```python +class GitOperations(Protocol): + def clone(self, repo_url: str, dest: Path, credential_id: str) -> None: ... + def fetch(self, repo_path: Path, credential_id: str) -> None: ... + def push(self, repo_path: Path, credential_id: str) -> None: ... + def get_status(self, repo_path: Path) -> dict[str, Any]: ... +``` + ### 3.2 RuntimeProvider ```python diff --git a/docs/development.md b/docs/development.md index 736509c..000a086 100644 --- a/docs/development.md +++ b/docs/development.md @@ -175,3 +175,23 @@ pnpm build - **Frontend**: React functional components, TypeScript strict mode, ESLint + Ruff-like rules. - **Backend**: FastAPI, Pydantic settings, pytest, ruff, mypy. - **Commits**: Conventional commits with task ID prefix, e.g. `feat(FN-002): description`. + +## Git Abstraction + +The `app/git/` package in the backend provides provider-independent Git +orchestration. It is split into two layers so that remote provider API logic +and local CLI operations evolve independently: + +| Module | Responsibility | +|--------|--------------| +| `types` | Enumerations (`ProviderKind`, `CredentialKind`, `ConnectionStatus`, `SshKeyStatus`) | +| `provider` | Abstract `GitProvider` — remote operations (`validate_connection`, `list_repositories`, `create_deploy_key`, …) | +| `credentials` | `GitCredential` / `AccessTokenCredential` models and `CredentialStorage` ABC | +| `ssh_key` | `SshKeyPair` model and `SshKeyLifecycle` (Ed25519 generation via `cryptography`) | +| `connection` | `RepositoryConnection` ORM mapping and `ConnectionManager` orchestration | +| `operations` | Abstract `GitOperations` and concrete `LocalGitOperations` (subprocess-based `get_status`) | + +Security rules for the package: +- Credential models store **only** `encrypted_payload` — no plaintext `token` or `private_key` fields. +- SSH private keys are encrypted before storage; the field uses `repr=False`. +- Real encryption of the payload is deferred to FN-009; the current placeholder is base64-only.