# Gateway ↔ repo-sync Integration Contract

**Version:** 1.1  
**Epic:** TW-98 (Gateway), TW-12 (repo-sync)  
**Related:** TW-142 (Import), TW-136/138 (Connect)  
**API reference:** [API-REFERENCE.md](./API-REFERENCE.md) (full request/response schemas; includes how to obtain `providerRepoIds`)

---

## Overview

The gateway stores per-org provider credentials (encrypted). repo-sync performs `git clone` and indexing. **Raw tokens never go to the browser.**

### Credential delivery (v1.1)

| Mode | When | How |
|---|---|---|
| **MVP — direct pass** | Import | Gateway mints short-lived credentials and includes `cloneAuth` in the `POST /repos/import` payload |
| **Optional — on-demand** | Re-sync, retries, delayed queues | repo-sync calls `GET /internal/v1/connections/:id/clone-auth` at clone time |

**MVP default:** direct pass. repo-sync must clone **immediately** (before `cloneAuth.expiresAt`) and **never persist** credentials.

### Supported providers

| `provider` value | Auth source | Clone host |
|---|---|---|
| `github` | GitHub App installation token | `github.com` |
| `bitbucket_cloud` | Bitbucket Cloud OAuth access token | `bitbucket.org` |
| `bitbucket_server` | HTTP access token (admedia only) | `bit.admedia.com` |

---

## 1. User-facing import (Gateway)

### `POST /api/v1/repos/import`

**Auth:** JWT, org owner, active subscription, plan repo limit.

**Request:**
```json
{
  "providerRepoIds": ["{uuid-or-id}", "..."]
}
```

**Gateway behavior:**
1. Resolve each ID in `ProviderRepo` for `req.user.orgId`.
2. Verify active `ProviderConnection` for the provider.
3. Enforce `requireRepoLimit` (count = array length).
4. Mint short-lived clone credentials per connection.
5. Call repo-sync `POST /repos/import` with structured payload including `cloneAuth`.
6. On repo-sync `2xx`, increment `OrgUsage.indexedRepoCount` by successful import count.

**Response:** Proxied from repo-sync (202 + job IDs).

---

## 2. repo-sync import API

### `POST /repos/import`

**Auth:** Trusted headers from gateway (`X-Org-Id`, `X-User-Id`, `X-Request-Id`).

**Request:**
```json
{
  "importJobId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "507f1f77bcf86cd799439011",
  "connectionId": "507f1f77bcf86cd799439012",
  "provider": "github",
  "cloneAuth": {
    "username": "x-access-token",
    "password": "<short-lived-token>",
    "expiresAt": "2026-06-18T15:30:00.000Z"
  },
  "repos": [
    {
      "providerRepoId": "123456789",
      "fullName": "my-org/my-repo",
      "defaultBranch": "main",
      "cloneUrl": "https://github.com/my-org/my-repo.git"
    }
  ]
}
```

| Field | Required | Description |
|---|---|---|
| `importJobId` | yes | UUID for idempotency |
| `orgId` | yes | Organization ID |
| `connectionId` | yes | `ProviderConnection._id` |
| `provider` | yes | `github` \| `bitbucket_cloud` \| `bitbucket_server` |
| `cloneAuth` | yes (MVP) | Short-lived git credentials — **in-memory only** |
| `cloneAuth.username` | yes | Git HTTP username (see provider table below) |
| `cloneAuth.password` | yes | Short-lived access token |
| `cloneAuth.expiresAt` | yes | ISO timestamp — clone must complete before this |
| `repos` | yes | Non-empty array |
| `repos[].providerRepoId` | yes | Provider-native repo ID |
| `repos[].fullName` | yes | `owner/repo` or `PROJECT/slug` |
| `repos[].defaultBranch` | yes | Branch to checkout |
| `repos[].cloneUrl` | no | Gateway precomputes HTTPS URL without credentials |

### Provider clone credentials

| Provider | `cloneAuth.username` | `cloneAuth.password` | Typical TTL |
|---|---|---|---|
| `github` | `x-access-token` | Installation access token | ~1 hour |
| `bitbucket_cloud` | `x-token-auth` | OAuth access token | Provider-defined |
| `bitbucket_server` | Bitbucket username | HTTP access token | Until revoked |

### repo-sync obligations (MVP)

- Clone **synchronously or immediately** after accepting the import — do not queue past `expiresAt`
- **Never** write `cloneAuth` to MongoDB, Redis, logs, or error reports
- Discard credentials from memory after clone completes or fails
- Use `cloneUrl` + `cloneAuth.username` + `cloneAuth.password` for `git clone`

**Response (202 Accepted):**
```json
{
  "importJobId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "accepted",
  "jobs": [
    {
      "jobId": "job_abc123",
      "providerRepoId": "123456789",
      "fullName": "my-org/my-repo",
      "status": "pending"
    }
  ]
}
```

**Errors:**

| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_PAYLOAD` | Missing/invalid fields |
| 409 | `DUPLICATE_JOB` | Same `importJobId` already processed |
| 503 | `TOKEN_MINT_FAILED` | Gateway could not mint credentials (returned to client via gateway) |

---

## 3. Internal clone-auth API (optional — re-sync / async)

Use when import did not include credentials, or credentials expired before clone ran.

### `GET /internal/v1/connections/:connectionId/clone-auth`

**Auth:** Header `X-Internal-Secret: <GATEWAY_INTERNAL_SERVICE_SECRET>`

**Query (required):** `fullName` — for clone URL and audit logging.  
**Query (optional):** `providerRepoId`

**Response (200):**
```json
{
  "connectionId": "507f1f77bcf86cd799439012",
  "provider": "github",
  "authMethod": "git_credential",
  "cloneUrl": "https://github.com/my-org/my-repo.git",
  "username": "x-access-token",
  "password": "<short-lived-token>",
  "expiresAt": "2026-06-18T15:30:00.000Z"
}
```

**Errors:**

| Status | Code | Meaning |
|---|---|---|
| 401 | `UNAUTHORIZED` | Invalid internal secret |
| 404 | `CONNECTION_NOT_FOUND` | Unknown connection |
| 410 | `CONNECTION_REVOKED` | Provider disconnected |
| 503 | `TOKEN_MINT_FAILED` | GitHub/Bitbucket API error |

---

## 4. repo_id convention (repo-sync)

```
{provider}:{fullName}
```

Examples: `github:my-org/my-repo`, `bitbucket_cloud:my-workspace/my-repo`

---

## 5. Clone URL builders (Gateway)

| Provider | Pattern |
|---|---|
| GitHub | `https://github.com/{owner}/{repo}.git` |
| Bitbucket Cloud | `https://bitbucket.org/{workspace}/{repo}.git` |
| Bitbucket Server | `{BITBUCKET_SERVER_BASE_URL}/scm/{project}/{slug}.git` |

---

## 6. Trusted headers (Gateway → repo-sync)

| Header | Description |
|---|---|
| `X-Request-Id` | Correlation ID |
| `X-Org-Id` | Organization |
| `X-User-Id` | Acting user |
| `X-Plan-Code` | Subscription plan |

---

## 7. Sequence (MVP — direct pass)

```
Client → POST /api/v1/repos/import
Gateway → validate + resolve ProviderRepo
Gateway → mint cloneAuth per connection
Gateway → POST repo-sync /repos/import  (includes cloneAuth)
repo-sync → git clone immediately with cloneAuth
repo-sync → snapshot + Redis events
Gateway ← 202 Accepted
```

## 7b. Sequence (optional — on-demand auth)

```
repo-sync → GET gateway /internal/.../clone-auth?fullName=...
repo-sync → git clone with returned credentials
```

Use for re-sync, webhook-triggered fetch, or async job queues.

---

## 8. Environment variables

### Gateway
```
GATEWAY_INTERNAL_SERVICE_SECRET=   # required for clone-auth and repo-sync /indexed-repos
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY=
BITBUCKET_CLIENT_ID=
BITBUCKET_CLIENT_SECRET=
BITBUCKET_SERVER_BASE_URL=https://bit.admedia.com
BITBUCKET_SERVER_USERNAME=service-account@admedia.com
BITBUCKET_SERVER_TOKEN=<http-access-token>
PROVIDER_TOKEN_ENCRYPTION_KEY=
REPO_SYNC_URL=
```

### repo-sync (MVP)
```
REPO_SYNC_URL=                     # listen port
GIT_WORKSPACE_PATH=
GATEWAY_INTERNAL_SERVICE_SECRET=   # must match gateway; required for GET /indexed-repos
```

### repo-sync (on-demand auth — later)
```
GATEWAY_INTERNAL_URL=https://api.lucos.com
GATEWAY_INTERNAL_SECRET=<same as GATEWAY_INTERNAL_SERVICE_SECRET>
```

---

## 9. Phasing

| Phase | Scope |
|---|---|
| MVP | Direct `cloneAuth` in import payload; immediate clone |
| v1.1+ | On-demand clone-auth for re-sync and async queues |
| v1.2 | Bitbucket Server (admedia) — gateway bootstrap + catalog sync + import |

---

## 10. Open questions

1. Partial import failure: increment usage per successful repo or all-or-nothing?
2. Re-import same repo: idempotent upsert or reject?
3. Async import queues: switch to on-demand clone-auth when job delay exceeds token TTL?

**Defaults for MVP:** Per-repo job status; usage increment on import 202; re-import allowed; **immediate clone required**.
