Files
headquarter/openspec/specs/config-profile-multi-repo-mounts.md
Alex Blank 0e6521e433 feat: config profile multi-repo mounts
- Add mappings array support to git_mount entries
- Clone repository once per git_mount entry, mount multiple subdirectories
- Normalize legacy source_path+target_path to mappings on read
- Update _merge_git_mounts to dedup by (remote_url, branch) and concatenate mappings
- Add _normalize_git_mount, _clone_git_repo, _resolve_git_mount_mappings helpers
- Update GitMountItem Pydantic model with GitMountMapping and model_validator
- Update frontend GitMountEditor component with mappings UI
- Auto-convert legacy git mount entries to mappings format on load
- Add 15 backend unit tests for normalization, resolution, and glob expansion
- Update existing config profile resolver tests for new merge behavior

Quality gates: pytest 167 passed, frontend typecheck clean

Addresses: config-profile-multi-repo-mounts
2026-05-28 23:35:22 +02:00

3.7 KiB

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)

{
  "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:

{"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:

{"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:

{"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