Documentation
¶
Overview ¶
Package github is bex-api's GitHub App integration (docs/ADR026-github-integration.md): a small client that signs the app JWT, mints short-lived installation tokens, and lists an installation's repositories — plus the workspace-connection service verbs over the control-plane store. The operator never imports this; bex-api mints tokens and writes them into a k8s Secret the build Job consumes.
Index ¶
- Variables
- type APIClient
- type APIError
- type Claim
- type ClaimCandidate
- type ClaimSelection
- type Client
- func (c *Client) AuthorizeURL() string
- func (c *Client) ClaimableInstallations(ctx context.Context, code string) ([]Installation, error)
- func (c *Client) GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)
- func (c *Client) GetFileContents(ctx context.Context, token, owner, repo, path, ref string) (FileContents, error)
- func (c *Client) GetInstallation(ctx context.Context, installationID int64) (Installation, error)
- func (c *Client) GetRepoCommitSHA(ctx context.Context, token, owner, repo, branch string) (string, error)
- func (c *Client) InstallURL() string
- func (c *Client) InstallVerificationConfigured() bool
- func (c *Client) ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
- func (c *Client) ListRepoTree(ctx context.Context, token, owner, repo, path, ref string) ([]RepoTreeEntry, error)
- func (c *Client) ListRepos(ctx context.Context, installationID int64) ([]Repo, error)
- func (c *Client) MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
- func (c *Client) MintSessionInstallationToken(ctx context.Context, installationID int64, repository string) (InstallationToken, error)
- func (c *Client) OpenDraftPullRequest(ctx context.Context, installationID int64, ...) (PullRequest, error)
- func (c *Client) RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)
- func (c *Client) Slug() string
- func (c *Client) VerifyInstallationAdmin(ctx context.Context, code string, installationID int64) (bool, error)
- type Commit
- type Config
- type Connection
- type ConnectionStore
- type FileContents
- type Installation
- type InstallationToken
- type InstallationVerifier
- type PullRequest
- type Repo
- type RepoTreeEntry
- type RepoTreeProbe
- type RuntimeDetection
- type Service
- func (s *Service) BlueprintFileFetcher() blueprintFetcher
- func (s *Service) DeployCommitSource() commitSource
- func (s *Service) DeployTokenSource() tokenSource
- func (s *Service) DetectRepoRuntime(ctx context.Context, ownerID, repoURL, branch, rootDir string) (RuntimeDetection, error)
- func (s *Service) Disconnect(ctx context.Context, ownerID string, installationID int64) error
- func (s *Service) GetClaimSelection(ctx context.Context, ownerID, selectionID string) (ClaimSelection, error)
- func (s *Service) GetConnection(ctx context.Context, ownerID string) (Connection, error)
- func (s *Service) GraphQLMutation() graphql.Fields
- func (s *Service) GraphQLQuery() graphql.Fields
- func (s *Service) InstallationResolver() installationResolver
- func (s *Service) ListBranches(ctx context.Context, ownerID, repoURL string) ([]string, error)
- func (s *Service) ListConnections(ctx context.Context, ownerID string) ([]Connection, error)
- func (s *Service) ListRepos(ctx context.Context, ownerID string) ([]Repo, error)
- func (s *Service) ProbeRepoTree(ctx context.Context, ownerID, repoURL, branch, rootDir string) (RepoTreeProbe, error)
- func (s *Service) RegisterMCP(srv *mcp.Server)
- func (s *Service) RegisterREST(mux *http.ServeMux)
- func (s *Service) SelectClaim(ctx context.Context, ownerID, selectionID string, installationID int64) (Connection, error)
- func (s *Service) StartClaim(ctx context.Context, ownerID string, installationID int64) (Claim, error)
- func (s *Service) StartConnect(ctx context.Context, ownerID string) (Connection, error)
Constants ¶
This section is empty.
Variables ¶
var ( ErrBranchNotFound = errors.New("blueprint branch not found") ErrRepoNotFoundOrNoAccess = errors.New("blueprint repository not found or not accessible") )
ErrBranchNotFound and ErrRepoNotFoundOrNoAccess name which half of a blueprint commit lookup failed. GitHub answers 404 for a missing branch and for a repository the installation cannot see, and the status alone cannot tell them apart — so callers that want to say "check the branch name" rather than "check the repository" need this, and guessing from message text is not a contract (w2/m97 t001).
The two are distinguished by the upstream response BODY, which stays internal: it is read here to pick a sentinel and is never interpolated into any error that reaches a client (w6/005). A private repository and a missing repository are deliberately one sentinel — GitHub makes them indistinguishable on purpose, and so must bex.
Functions ¶
This section is empty.
Types ¶
type APIClient ¶
type APIClient interface {
InstallURL() string
GetInstallation(ctx context.Context, installationID int64) (Installation, error)
ListRepos(ctx context.Context, installationID int64) ([]Repo, error)
ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
ListRepoTree(ctx context.Context, token, owner, repo, path, ref string) ([]RepoTreeEntry, error)
MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)
GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)
GetFileContents(ctx context.Context, token, owner, repo, path, ref string) (FileContents, error)
GetRepoCommitSHA(ctx context.Context, token, owner, repo, branch string) (string, error)
OpenDraftPullRequest(ctx context.Context, installationID int64, owner, repo, head, base, title, body string) (PullRequest, error)
}
APIClient is the GitHub REST surface the Service uses — *Client in production, a fake in tests. nil => the GitHub App is unconfigured (BEX_GITHUB_APP_* unset) and every verb reports core.ErrGitHubUnavailable.
type APIError ¶
APIError is a non-2xx GitHub response. Callers map it to a clean bex error (never a raw 500) so a GitHub outage surfaces as "GitHub said N", not a panic.
type Claim ¶
type Claim struct {
ClaimURL string `json:"claimUrl"`
}
Claim is StartClaim's result: the GitHub OAuth authorize URL that starts the ADR078 §3a claim flow for an already-installed account.
type ClaimCandidate ¶
type ClaimCandidate struct {
InstallationID int64 `json:"installationId"`
AccountLogin string `json:"accountLogin"`
}
ClaimCandidate is one option the picker renders.
type ClaimSelection ¶
type ClaimSelection struct {
ID string `json:"id"`
Candidates []ClaimCandidate `json:"candidates"`
ExpiresAt string `json:"expiresAt"`
}
ClaimSelection is the picker's view of an outstanding ambiguous claim.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the GitHub REST API as a GitHub App. It is safe for concurrent use.
func NewClient ¶
NewClient parses the config (numeric app id, PEM private key) and returns a ready client. It errors if any field is missing or malformed.
func (*Client) AuthorizeURL ¶
AuthorizeURL is the GitHub OAuth user-authorization endpoint for this app — the one GitHub flow that always carries `state` through, which is what makes the ADR078 §3a claim flow possible for already-installed accounts (the install URL's Configure path strips state). The caller appends &state=<signed token>.
func (*Client) ClaimableInstallations ¶
ClaimableInstallations resolves the ADR078 §3a claim callback's missing installation id server-side: it exchanges the OAuth `code` for a user token, lists THIS app's installations the authorizing user can reach (GET /user/installations — the endpoint is app-scoped by the user-to-server token), and keeps only those the user ADMINISTERS — the same proof VerifyInstallationAdmin applies (exact personal-account owner, or active org membership with role admin). Reachability alone (an org member who can see the installation) is never an administration proof. A declined code exchange yields zero candidates (the flow must be restarted), not an error.
func (*Client) GetCommit ¶
GetCommit resolves ref (a branch name, tag, or SHA) to the exact commit it points at (GET /repos/{owner}/{repo}/commits/{ref}). An unknown ref or an out-of-grant repo returns an *APIError (404/422) — callers treat those as "unresolvable", not a failure.
func (*Client) GetFileContents ¶
func (*Client) GetInstallation ¶
GetInstallation fetches one installation by id (GET /app/installations/{id}), authenticated as the app. A non-existent or inaccessible id returns an *APIError (typically 404) — the authenticity check for the connect callback.
func (*Client) GetRepoCommitSHA ¶
func (c *Client) GetRepoCommitSHA(ctx context.Context, token, owner, repo, branch string) (string, error)
GetRepoCommitSHA resolves the HEAD commit SHA for a branch using the branch info endpoint. Used by the blueprint fetcher to stamp the commitID on each sync run when we already hold the file contents.
func (*Client) InstallURL ¶
InstallURL is where a workspace admin installs the app (and grants repos).
func (*Client) InstallVerificationConfigured ¶
InstallVerificationConfigured reports whether the OAuth credentials needed to verify installation administration are present (F2). The composition root wires the Service's Verifier only when this is true.
func (*Client) ListBranches ¶
func (c *Client) ListBranches(ctx context.Context, installationID int64, owner, repo string) ([]string, error)
ListBranches returns a bounded branch inventory of owner/repo, following pagination (per_page=100, `Link` rel="next"). It mints a fresh installation token first. (w5/m54 — feeds the dashboard's searchable Branch combobox.)
func (*Client) ListRepoTree ¶
func (c *Client) ListRepoTree(ctx context.Context, token, owner, repo, path, ref string) ([]RepoTreeEntry, error)
ListRepoTree returns one non-recursive directory listing at path and ref. The contents response is bounded because this runs interactively from the create wizard and must not let one repository occupy an API worker with an unbounded body. A missing path remains an *APIError{Status: 404}; the service converts it (and other probe failures) to its typed unknown outcome.
func (*Client) ListRepos ¶
ListRepos returns the bounded repository inventory the installation can access, following pagination (per_page=100, GitHub `Link` header rel="next"). Page, item, byte, origin, cycle, and wall-clock limits prevent a connected installation from consuming an API worker or GitHub quota indefinitely.
func (*Client) MintInstallationToken ¶
func (c *Client) MintInstallationToken(ctx context.Context, installationID int64) (InstallationToken, error)
MintInstallationToken exchanges the app JWT for a 1h installation access token for the given installation. The existing deploy/list/commit path needs all installation repositories but remains explicitly read-only even though the App itself now holds contents:write for agent sessions.
func (*Client) MintSessionInstallationToken ¶
func (c *Client) MintSessionInstallationToken(ctx context.Context, installationID int64, repository string) (InstallationToken, error)
MintSessionInstallationToken mints the least-privilege Git credential used by one agent session. GitHub's request accepts repository NAMES (the installation itself fixes the owner), so callers must separately bind owner/account before reaching this method. Contents write includes clone/fetch read access and push; metadata stays read-only. The token expires after GitHub's fixed one-hour TTL.
func (*Client) OpenDraftPullRequest ¶
func (c *Client) OpenDraftPullRequest(ctx context.Context, installationID int64, owner, repo, head, base, title, body string) (PullRequest, error)
OpenDraftPullRequest opens a draft PR from head→base on owner/repo using an installation token narrowed to that one repository with pull_requests:write. One branch, one PR per session (Copilot model): when a PR already exists for the head branch (GitHub answers 422), the existing open PR is returned so a steering turn updates the same PR instead of failing. The token is minted per call, is repository-scoped, and is never returned to the caller.
func (*Client) RepoAccessible ¶
RepoAccessible reports whether the given installation token can reach owner/repo (GET /repos/{owner}/{repo}). An installation token only reaches repos in the installation's grant, so 404 => not granted (ok=false, no error); 2xx => granted; any other status is an *APIError.
func (*Client) VerifyInstallationAdmin ¶
func (c *Client) VerifyInstallationAdmin(ctx context.Context, code string, installationID int64) (bool, error)
VerifyInstallationAdmin reports whether the GitHub user identified by the install-callback OAuth `code` actually administers installationID — the principal proof the connect callback needs so an App-JWT lookup is never mistaken for ownership (F2). It exchanges the code for a short-lived user token, resolves the installation's owning account, and requires either the exact personal-account owner or an active organization membership with role `admin`. Merely seeing an installation or one selected repository is not an administration proof.
type Commit ¶
type Commit struct {
SHA string `json:"sha"`
Message string `json:"message"`
AuthorAt *time.Time `json:"authorAt,omitempty"`
}
Commit is the resolved tip of a ref — the SHA plus its message and author timestamp (w9/001 + w2/m42). The subset of GitHub's commit object the deploy path stamps onto a deploy row as provenance.
type Config ¶
type Config struct {
AppID string // numeric GitHub App id (the JWT `iss`)
PrivateKey string // RSA private key, PEM (out-of-band secret)
Slug string // app slug, builds the install URL
// ClientID / ClientSecret are the App's OAuth credentials, used ONLY to verify
// that the user completing an install actually administers the installation
// (F2, docs/ADR026-github-integration.md). Both empty leaves existing
// connection reads/deploys available but makes every new binding fail closed;
// exactly one set is invalid configuration. The App must enable "Request user
// authorization (OAuth) during installation" so the callback carries a code.
ClientID string
ClientSecret string
}
Config is the GitHub App configuration read once at startup from BEX_GITHUB_APP_ID / BEX_GITHUB_APP_PRIVATE_KEY / BEX_GITHUB_APP_SLUG. Any field empty (or an unparseable id/key) => NewClient errors and the caller leaves the github service unconfigured (503).
type Connection ¶
type Connection struct {
Connected bool `json:"connected"`
AccountLogin string `json:"accountLogin,omitempty"`
InstallationID int64 `json:"installationId,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
InstallURL string `json:"installUrl"`
}
Connection is the neutral connection view every adapter renders. InstallURL is always populated (the connect CTA the human clicks); the rest are set only when Connected.
type ConnectionStore ¶
type ConnectionStore interface {
// BindGitConnection atomically enforces BOTH quotas — the workspace's
// connection fan-in and the installation's workspace fan-out (ADR078 §2) —
// while inserting or refreshing one binding.
BindGitConnection(ctx context.Context, c store.GitConnection, maxConnections, maxWorkspaces int) (store.GitConnection, error)
GetGitConnection(ctx context.Context, workspaceID string) (store.GitConnection, error)
// ListGitConnections returns a workspace's full connection set, oldest first
// (ADR078) — the multi-account aggregate the repo picker and list surface read.
ListGitConnections(ctx context.Context, workspaceID string) ([]store.GitConnection, error)
// GetGitConnectionByOwner resolves the connection whose account login matches a
// repo's owner — the exact installation to mint that repo's token from (ADR078 §4).
GetGitConnectionByOwner(ctx context.Context, workspaceID, accountLogin string) (store.GitConnection, error)
// GitConnectionsByInstallation resolves every workspace that has proved a
// binding for an installation (ADR078 §2, N:N). The push webhook's reverse
// lookup; empty means act on nothing (§4a).
GitConnectionsByInstallation(ctx context.Context, installationID int64) ([]store.GitConnection, error)
// CountGitConnections backs the per-workspace connection quota (ADR078 §2).
CountGitConnections(ctx context.Context, workspaceID string) (int, error)
DeleteGitConnection(ctx context.Context, workspaceID string, installationID int64) error
// The subject-bound, single-use connect transaction (w1/m67 F3): the record
// that ties "who started this flow" to "who came back from GitHub".
CreateGitHubConnectTransaction(ctx context.Context, t store.GitHubConnectTransaction) error
ConsumeGitHubConnectTransaction(ctx context.Context, nonce string) (store.GitHubConnectTransaction, error)
// The deferred claim selector (ADR078 §3a): an ambiguous claim's already-proved
// candidate set, held single-use for the few minutes the human needs to choose.
CreateGitHubClaimSelection(ctx context.Context, sel store.GitHubClaimSelection) error
GetGitHubClaimSelection(ctx context.Context, id string) (store.GitHubClaimSelection, error)
ConsumeGitHubClaimSelection(ctx context.Context, id string) (store.GitHubClaimSelection, error)
}
ConnectionStore is the Service's seam to the control-plane store — the narrow slice of Store it needs. *store.PGStore satisfies it; a fake backs the tests. nil => the control-plane store is off (BEX_CP_DB_URI unset) and every verb reports core.ErrGitHubUnavailable.
type FileContents ¶
type FileContents struct {
Contents string
}
FileContents is the decoded content of a repository file (w2/m62 — blueprint manifest fetch). The caller names the exact ref (branch or immutable commit) it wants; provenance comes from the ref it passed, never from this struct — w8/m36 removed the old blob-SHA-as-commit fallback, which fabricated sync provenance whenever branch resolution failed.
type Installation ¶
type Installation struct {
ID int64 `json:"id"`
AccountLogin string `json:"accountLogin"`
AccountType string `json:"accountType"`
}
Installation identifies a GitHub App installation and the account it belongs to. Fetched with the app JWT, so a forged/unknown id fails authentication — which is how Connect validates a browser-supplied installation_id.
type InstallationToken ¶
type InstallationToken struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
}
InstallationToken is a short-lived (1h) installation access token.
type InstallationVerifier ¶
type InstallationVerifier interface {
VerifyInstallationAdmin(ctx context.Context, code string, installationID int64) (bool, error)
// AuthorizeURL is the app's OAuth user-authorization endpoint — the claim
// flow's start, the one GitHub flow that always preserves `state` (§3a).
AuthorizeURL() string
// ClaimableInstallations resolves the claim callback's missing installation
// id: this app's installations the code's user ADMINISTERS.
ClaimableInstallations(ctx context.Context, code string) ([]Installation, error)
}
InstallationVerifier proves the user completing a browser flow actually administers the installation being bound (F2), and powers the ADR078 §3a claim flow for already-installed accounts. Implemented by *Client when the App's OAuth credentials are configured; nil => connect/claim starts refuse up front (§7) and the callback fails closed. Kept an interface so the fake in tests can drive accept/reject.
type PullRequest ¶
type PullRequest struct {
Number int `json:"number"`
HTMLURL string `json:"htmlUrl"`
State string `json:"state"`
Draft bool `json:"draft"`
}
PullRequest is the subset of a GitHub pull request the agent-session delivery path records (ADR047 D4). JSON tags are bex-api's camelCase shape, not GitHub's snake_case wire shape.
type Repo ¶
type Repo struct {
ID int64 `json:"id"`
FullName string `json:"fullName"`
Private bool `json:"private"`
DefaultBranch string `json:"defaultBranch"`
HTMLURL string `json:"htmlUrl"`
CloneURL string `json:"cloneUrl"`
// AccountLogin and InstallationID are set by the service when it aggregates
// repos across a workspace's several connections (ADR078 §4), so the picker
// can group repos by GitHub account. The client itself leaves them zero.
AccountLogin string `json:"accountLogin,omitempty"`
InstallationID int64 `json:"installationId,omitempty"`
}
Repo is the subset of a GitHub repository the repo picker and deploy path need. JSON tags are the bex-api camelCase shape (identical across surfaces), not GitHub's snake_case wire shape (decoded via ghRepo below).
type RepoTreeEntry ¶
RepoTreeEntry is one immediate child returned by GitHub's contents API. The service keeps only Type == "file" before runtime detection; retaining Type at this seam prevents a same-named directory from masquerading as a manifest.
type RepoTreeProbe ¶
type RepoTreeProbe struct {
Entries []RepoTreeEntry
Unknown bool
}
RepoTreeProbe is the typed result of the best-effort repository listing used by runtime detection. Unknown is deliberately data, not an error: a missing directory, empty repository, rate limit, or GitHub outage must leave the create wizard on its existing manual runtime selection path.
type RuntimeDetection ¶
RuntimeDetection is the dashboard-facing verdict for one repository directory. Empty fields mean unknown; GraphQL renders them as null.
func DetectRuntime ¶
func DetectRuntime(entries []RepoTreeEntry) RuntimeDetection
DetectRuntime is a pure manifest-to-runtime mapping. Dockerfile wins; one unique native runtime wins; no signal or conflicting native signals are unknown. Multiple manifests for the same runtime (Python's two supported forms) are not ambiguous and retain the first table entry as evidence.
type Service ¶
type Service struct {
*core.Base
GitHub APIClient // nil => GitHub App unconfigured
Store ConnectionStore // nil => control-plane store off
// Verifier proves installation administration on the browser connect callback
// (F2). nil => not configured; the unique-binding gate still applies.
Verifier InstallationVerifier
// StateSecret signs the short-lived workspace credential carried through the
// browser install redirect. Production reuses BEX_GITHUB_APP_PRIVATE_KEY's
// PEM bytes, so no second platform secret or replica-local state is needed.
StateSecret []byte
// DashboardURL is BEX_DASHBOARD_URL — where the install callback redirects
// the browser after success or with a bounded failure code. Empty => the
// callback returns JSON instead of redirecting.
DashboardURL string
// MaxConnections caps how many GitHub installations ONE workspace may connect
// (BEX_MAX_GIT_CONNECTIONS_PER_WORKSPACE, ADR078 §2; default 10, 0 disables).
// Bounds one tenant's connection fan-in — and therefore the per-connection
// GitHub round trips ListRepos makes.
MaxConnections int
// MaxWorkspacesPerInstallation is MaxConnections' mirror under N:N
// (BEX_MAX_WORKSPACES_PER_GIT_INSTALLATION, ADR078 §2; default 10, 0
// disables): how many workspaces ONE installation may serve, which is also
// how wide a single push delivery can fan out (§4a).
MaxWorkspacesPerInstallation int
// contains filtered or unexported fields
}
Service manages a workspace's GitHub App connection and lists its repos over the injected client + store. Both seams must be present; either nil => 503.
func (*Service) BlueprintFileFetcher ¶
func (s *Service) BlueprintFileFetcher() blueprintFetcher
BlueprintFileFetcher returns the blueprint-file-fetch seam wired in the composition root onto apps.Service.
func (*Service) DeployCommitSource ¶
func (s *Service) DeployCommitSource() commitSource
DeployCommitSource returns the deploy path's commit-resolution seam (wired onto deploys.Service and apps.Service in the composition root).
func (*Service) DeployTokenSource ¶
func (s *Service) DeployTokenSource() tokenSource
DeployTokenSource returns the deploy path's clone-token seam (wired onto apps.Service in the composition root).
func (*Service) DetectRepoRuntime ¶
func (s *Service) DetectRepoRuntime(ctx context.Context, ownerID, repoURL, branch, rootDir string) (RuntimeDetection, error)
DetectRepoRuntime combines the authorized GitHub probe with the pure heuristic. Probe failures and unrecognized trees intentionally return an empty verdict without a transport error so the wizard remains manual.
func (*Service) Disconnect ¶
Disconnect removes one of ownerID's connections ("" => the caller's default workspace, w6/m18). installationID names the exact connection to remove; 0 targets the sole connection (the singular-alias behavior) and is refused with ErrConflict when the workspace holds several — an ambiguous "disconnect" must not silently pick one. Idempotent: disconnecting when not connected is a no-op success. Admin-only.
func (*Service) GetClaimSelection ¶
func (s *Service) GetClaimSelection(ctx context.Context, ownerID, selectionID string) (ClaimSelection, error)
GetClaimSelection renders an outstanding selection for the picker WITHOUT consuming it. Admin-gated on ownerID's workspace ("" => the caller's default, ADR078 §6) and subject-matched on top: a selection id is a name, not a capability, so it reveals its candidates only to the bex user who started the claim, inside the workspace that claim was for. Unknown, expired, foreign- workspace and foreign-subject selections are all refused identically.
func (*Service) GetConnection ¶
GetConnection returns ownerID's connection status ("" => the caller's default workspace, w6/m18) — the singular compatibility alias over the workspace's oldest connection (ADR078). "Not connected" is a valid state, not an error, and (ADR078 §3) carries NO install URL: the bare, stateless URL is no longer advertised as a connect CTA — only the connectGit mutation mints a bindable (stateful) one. A connected row keeps its install URL as a "configure grants on GitHub" deep link. Member read.
func (*Service) GraphQLMutation ¶
GraphQLMutation returns connectGit (returns the connection + install URL), claimGit (the ADR078 §3a claim flow for already-installed accounts), and disconnectGit.
func (*Service) GraphQLQuery ¶
GraphQLQuery returns the gitConnections + gitConnection + repos queries.
func (*Service) InstallationResolver ¶
func (s *Service) InstallationResolver() installationResolver
InstallationResolver returns the webhook's installation→workspace seam (wired onto apps.GitWebhook in the composition root, codex #7).
func (*Service) ListBranches ¶
ListBranches returns the branch names of repoURL for ownerID's connected installation ("" => the caller's default workspace). It degrades to an empty list — never an error — for a non-github.com repo, no connection, or a repo the installation can't see, so the dashboard falls back to free-text branch entry (w5/m54). Member read.
func (*Service) ListConnections ¶
ListConnections returns every GitHub installation ownerID's workspace has connected ("" => the caller's default workspace), oldest first — the multi-account surface (ADR078 §5). An empty slice (never an error) means no connection; the caller starts one through the connectGit mutation. Each row's InstallURL is the bare "configure grants on GitHub" deep link. Member read.
func (*Service) ListRepos ¶
ListRepos returns the repositories across ALL of ownerID's connected installations ("" => the caller's default workspace, w6/m18; private included), each annotated with the GitHub account it came from so the picker can group by account (ADR078 §4). With no connection the list is empty (not an error). One GitHub round trip per connection, run through a fixed worker pool; a single connection's failure degrades that account's slice (logged) rather than failing the whole list. Member read.
func (*Service) ProbeRepoTree ¶
func (s *Service) ProbeRepoTree(ctx context.Context, ownerID, repoURL, branch, rootDir string) (RepoTreeProbe, error)
ProbeRepoTree returns the immediate files at rootDir on branch for a repo in ownerID's connected GitHub installation. It is member-readable like ListRepos and ListBranches. Expected probe failures collapse to Unknown so transport adapters never need to understand GitHub's rate-limit or contents dialect.
func (*Service) RegisterMCP ¶
RegisterMCP adds the git-connect tools to the shared MCP server.
func (*Service) RegisterREST ¶
RegisterREST mounts the GitHub-connect surface. `GET /v1/repos` and the connection verbs are bex extensions (Render exposes repos only via its private dashboard API); naming follows Render's kebab-case noun style. The callback is GitHub's post-install "Setup URL" redirect target. Browser callbacks carry a short-lived signed state credential instead of a dashboard cookie. Every binding also carries GitHub's single-use user-authorization code; bearer authentication alone is never an installation-ownership proof.
func (*Service) SelectClaim ¶
func (s *Service) SelectClaim(ctx context.Context, ownerID, selectionID string, installationID int64) (Connection, error)
SelectClaim completes an ambiguous claim by binding one installation the callback already proved. Admin-gated on ownerID's workspace.
SECURITY: this grants nothing the callback had not established. The selection is consumed atomically (so a replay finds nothing), can_manage is re-checked NOW rather than inherited from the callback (a demotion inside the selection window must not still bind), the presenting subject must equal the initiator, the selection's workspace must be the authorized one, and the installation must be a member of the stored set — so the client chooses among proved options and can never introduce a new one.
func (*Service) StartClaim ¶
func (s *Service) StartClaim(ctx context.Context, ownerID string, installationID int64) (Claim, error)
StartClaim begins the ADR078 §3a claim flow: bind an installation that ALREADY exists on GitHub (the direct-install case) to ownerID's workspace ("" => the caller's default). GitHub strips the signed state from the install URL for already-installed accounts, so the claim rides the OAuth user-authorization flow instead — the one flow that always preserves state — and the callback resolves the installation server-side from the authorizing user's admin set. Admin-only, same transaction record as StartConnect (w1/m67 F3).
installationID (0 = unspecified) is the optional start-time selector: it NARROWS the candidate set the callback proves. It is not trusted as an input — an installation the authorizing GitHub user does not administer never becomes a candidate no matter what is named here.
func (*Service) StartConnect ¶
StartConnect returns the current connection state plus the install URL the admin clicks to install the app (and grant repos). Admin-only — connecting a workspace's GitHub is an admin action even though the record lands at the callback. ownerID ("" => the caller's default workspace, w6/m18) names the workspace to check/connect, membership-checked via core.WithWorkspace like every other explicit-target verb. The returned install URL carries that resolved workspace in a short-lived signed state credential, so GitHub's identity-less callback can safely record against the same workspace.