# Spec: Config Profile Multi-Repo Mounts ## Requirements ### Functional 1. **FR-1**: A `git_mount` entry MAY include a `mappings` array. 2. **FR-2**: Each item in `mappings` MUST have `source_path` and `target_path`. 3. **FR-3**: If `mappings` is absent, `source_path` and `target_path` at the entry level MUST be treated as a single mapping (backward compatibility). 4. **FR-4**: The repository MUST be cloned exactly once per `git_mount` entry. 5. **FR-5**: Each mapping MUST create a separate Docker bind mount. 6. **FR-6**: Glob patterns in `source_path` MUST be expanded per mapping. 7. **FR-7**: Relative `target_path` values MUST be resolved against `working_directory`. 8. **FR-8**: The merge logic for included profiles MUST deduplicate by `(remote_url, branch)` within a single resolved profile's `git_mounts` list. ### Non-Functional 1. **NFR-1**: No database schema migration required. 2. **NFR-2**: Existing API responses must remain backward-compatible. 3. **NFR-3**: Frontend type-check must pass without errors. ## API Contracts ### ConfigProfile model (git_mounts field) ```json { "git_mounts": [ { "remote_url": "https://github.com/user/monorepo.git", "branch": "main", "mappings": [ {"source_path": "packages/api", "target_path": "/app/api"}, {"source_path": "packages/web", "target_path": "/app/web"} ] }, { "remote_url": "https://github.com/user/docs.git", "source_path": ".", "target_path": "/docs", "branch": "main" } ] } ``` ### Validation rules 1. `mappings` must be a non-empty array if present. 2. Each mapping must have `source_path` (string) and `target_path` (string). 3. Either `mappings` OR (`source_path` AND `target_path`) must be present. 4. `remote_url` must be a valid HTTPS or SSH Git URL. ## Database Schema No changes. `git_mounts` is stored as JSONB in `config_profiles.git_mounts`. ## Scenarios ### Scenario 1: Monorepo with multiple packages **Given** a Config Profile with: ```json {"git_mounts": [{ "remote_url": "https://github.com/corp/monorepo.git", "branch": "main", "mappings": [ {"source_path": "packages/api", "target_path": "/app/api"}, {"source_path": "packages/web", "target_path": "/app/web"}, {"source_path": "packages/shared", "target_path": "/app/shared"} ] }]} ``` **When** the profile is applied to an instance **Then**: 1. `corp/monorepo` is cloned once to `git-mounts/monorepo-{hash}/repo-clone` 2. Three bind mounts are created: - `{clone}/packages/api` → `/app/api` - `{clone}/packages/web` → `/app/web` - `{clone}/packages/shared` → `/app/shared` ### Scenario 2: Legacy single mapping (backward compatibility) **Given** a Config Profile with: ```json {"git_mounts": [{ "remote_url": "https://github.com/user/repo.git", "source_path": "src", "target_path": "/workspace/src", "branch": "main" }]} ``` **When** the profile is applied **Then** the behavior is identical to before (single clone, single mount). ### Scenario 3: Glob expansion within mapping **Given** a mapping with: ```json {"source_path": "packages/*", "target_path": "/app/packages"} ``` **When** the repo is cloned and the glob is expanded **Then** each matched directory is mounted as a separate bind mount with the relative path appended to the target: - `{clone}/packages/api` → `/app/packages/api` - `{clone}/packages/web` → `/app/packages/web` ## Test Strategy 1. Unit test `_resolve_single_git_mount` with `mappings` array 2. Unit test `_merge_git_mounts` with mappings deduplication 3. Integration test: profile with 3 mappings from same repo → verify single clone 4. Integration test: legacy profile without `mappings` → verify backward compatibility 5. Frontend unit test: GitMountEditor renders mappings form correctly