Skip to content
Merged
66 changes: 58 additions & 8 deletions apps/docs/src/content/docs/reference/bridge-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ adds ship management, a repo registry, and an aggregate system-resources view.
| `GET /workspaces` | Same path. Merged across ships, deduped, each row gains `ship`. |
| `GET /workspaces/:repo/:name` | Same path. Proxied live to the owning ship; response gains `ship` on **both** the `active` and `inactive` variants. |
| `GET /workspaces/:repo/:name/diff` | Same path and query. Proxied verbatim. |
| `POST /workspaces` | Same path, **different body**: `{ship, repoName, name, branch}` instead of `{url, repoName, name, branch}`. The clone URL comes from the bridge's repo registry. Response gains `ship`. |
| `POST /workspaces` | Same path, **different body**: `{ship, repoName, name, branch \| issueNumber}` instead of `{url, repoName, name, branch}`. The clone URL comes from the bridge's repo registry, and the branch may be named outright or derived from an issue. Response gains `ship`. |
| `POST /workspaces/:repo/:name/branch` | Same. |
| `POST /workspaces/:repo/:name/activate` | Same. |
| `POST /workspaces/:repo/:name/deactivate` | Same. |
Expand All @@ -36,7 +36,8 @@ adds ship management, a repo registry, and an aggregate system-resources view.

Bridge-only routes: `GET`/`POST /ships`, `DELETE /ships/:name`,
`GET /ships/:ship/system-resources`, `GET`/`POST /repos`,
`DELETE /repos/:name`, `GET /armory/file`, `GET /armory/ships`.
`DELETE /repos/:name`, `GET /repos/:name/branches`, `GET /armory/file`,
`GET /armory/ships`.

## Routes at a glance

Expand All @@ -50,6 +51,7 @@ Bridge-only routes: `GET`/`POST /ships`, `DELETE /ships/:name`,
| GET | `/repos` | 200 | `Repo[]` |
| POST | `/repos` | 201 | `Repo` |
| DELETE | `/repos/:name` | 200 | `{ ok: true }` |
| GET | `/repos/:name/branches` | 200 | `RepoBranch[]` |
| GET | `/armory` | 200 | `ArmoryManifest` |
| GET | `/armory/file` | 200 | `ArmoryFile` |
| GET | `/armory/ships` | 200 | `ShipArmoryState[]` |
Expand All @@ -76,11 +78,11 @@ The status comes from the thrown `BridgeError`; anything else is a `500`.

| Status | Raised when |
| --- | --- |
| `400` | Invalid repo/workspace/ship identifier; `unknown ship: <name>` (create, or per-ship resources); `unknown repo: <name>`; `invalid repo`. |
| `400` | Invalid repo/workspace/ship identifier; `unknown ship: <name>` (create, or per-ship resources); `unknown repo: <name>`; `invalid repo`; a create naming both a `branch` and an `issueNumber`, neither, a blank `branch`, or an `issueNumber` that is not a positive integer. |
| `404` | `workspace not found: <repo>/<name>` — no ship in the ownership index holds it; `ship not found: <name>`; `repo not found: <name>`. |
| `409` | `ship already registered: <name>`; a registering ship holds workspaces already hosted elsewhere; `workspace already exists: <repo>/<name>`; a create already in progress or of indeterminate outcome for that key; a ship removed mid-request. |
| `422` | Elysia schema validation on the request body. |
| `502` | `ship at <url> did not respond: <message>` (`POST /ships`); a ship returned no data, an invalid summary/status, or a workspace identity that was not requested. |
| `502` | `ship at <url> did not respond: <message>` (`POST /ships`); a ship returned no data, an invalid summary/status, or a workspace identity that was not requested; `GET /repos/:name/branches` could not reach the remote. |
| `503` | `ship "<name>" hosting <repo>/<name> is offline`; `ship "<name>" is offline` (create, per-ship resources); `ship "<name>" unreachable: <message>`. |
| ship's status | Any error the owning ship returned is passed through with the ship's own status and message. |

Expand Down Expand Up @@ -218,6 +220,30 @@ Responds `{ ok: true }`.

Deleting a repo does not touch any workspace already cloned from it.

### `GET /repos/:name/branches`

The branches the repo's remote currently advertises, sorted by name.

```ts
{ name: string; sha: string }[]
```

Answered with `git ls-remote --heads` against the registered clone URL, **not**
through the repo's provider: `provider` defaults to `"custom"`, for which no
provider exists, so a provider-backed listing would be unavailable for most
repos. `ls-remote` works against any git URL and needs no token. The probe runs
non-interactively — git never prompts for credentials or host keys, aborts an
http transfer that stalls for 15 s, and is given up on entirely after 20 s.

`refs/heads/` is stripped from each name; tags and other refs are omitted, so a
tag the ship would happily clone does not appear here.

| Status | Cause |
| --- | --- |
| `400` | Invalid repo identifier. |
| `404` | `repo not found: <name>`. |
| `502` | `could not list branches for repo "<name>": <git's stderr>` — unreachable, unauthenticated, or timed out. Credentials embedded in the repo URL are redacted from this message. |

## Armory

The read side of the [armory](/guides/the-armory/): the manifest of the bridge's
Expand Down Expand Up @@ -374,8 +400,10 @@ text.
### `POST /workspaces`

```ts
// request body — all four fields required
{ ship: string; repoName: string; name: string; branch: string }
// request body — ship, repoName and name are required;
// exactly one of branch / issueNumber must be present
{ ship: string; repoName: string; name: string;
branch?: string; issueNumber?: number }
```

`ship` names the target host and `repoName` must be a **registered repo**; the
Expand All @@ -387,10 +415,32 @@ bridge looks up its clone URL and calls the ship's `POST /workspaces` with
created in the new workspace rather than rejected — see
[ship API](/reference/ship-api/).

With `issueNumber` instead, the bridge resolves the branch itself before calling
the ship:

1. it reads the issue through the repo's [provider](#repo-registry) — so this
form needs `provider: "github"` and a token with repo write scope;
2. it computes the issue's canonical branch name, `<number>-<slug of title>`
capped at 60 characters (`12-better-create-workspace-issue`) — the same
function a client can use to preview the name;
3. it asks the provider to create that branch and record it as the issue's
linked development branch (GitHub's "Development → create a branch");
4. the **name the provider returns** is what the ship is told to check out. Use
it rather than the computed name — a provider may hand back a different ref.

Step 3 is safe to repeat. If the provider will not create the branch, the bridge
looks for one of the *same name* — first among the issue's linked branches, then
as a plain ref (someone may have pushed it by hand) — and uses that. A branch
linked to the issue under a **different** name is never substituted; that case
fails with `409`, since silently checking out a branch the caller never named is
worse than refusing. The branch is created before the clone and is not removed if
the clone then fails, so a retry reuses it.

| Status | Cause |
| --- | --- |
| `422` | A body field is missing. |
| `400` | Invalid repo/workspace identifier; `unknown ship: <ship>`; `unknown repo: <repoName>`. |
| `422` | `ship`, `repoName` or `name` is missing. |
| `400` | Invalid repo/workspace identifier; `unknown ship: <ship>`; `unknown repo: <repoName>`; both `branch` and `issueNumber`, or neither; a blank `branch`; an `issueNumber` that is not a positive integer. |
| provider's status | Any error resolving or linking the issue is passed through with the provider's own status — e.g. `401` with no token, `403` for a token without repo write scope, `404` for an unknown issue, `409` when the branch could be neither created nor found under the requested name. |
| `503` | `ship "<ship>" is offline`. |
| `409` | `workspace already exists: <repo>/<name>`; a create for that key is already in progress; the key's create outcome is indeterminate; the target ship was removed mid-request. |
| `502` | The ship returned no data, an invalid summary, or a different workspace identity. |
Expand Down
1 change: 1 addition & 0 deletions packages/fleet-bridge/src/api/repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function reposPlugin(manager: FleetManager) {
return { ok: true as const };
})
.get("/repos/:name/info", ({ params }) => manager.repoInfo(params.name))
.get("/repos/:name/branches", ({ params }) => manager.listRepoBranches(params.name))
.get(
"/repos/:name/issues",
({ params, query }) => manager.listRepoIssues(params.name, { state: query.state }),
Expand Down
5 changes: 4 additions & 1 deletion packages/fleet-bridge/src/api/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ export function workspacesPlugin(manager: FleetManager) {
ship: t.String(),
repoName: t.String(),
name: t.String(),
branch: t.String(),
// Either/or, enforced by the manager rather than the schema so the
// client gets a 400 with a reason instead of a shapeless 422.
branch: t.Optional(t.String()),
issueNumber: t.Optional(t.Numeric()),
}),
},
)
Expand Down
Loading
Loading