github

package
v0.0.0-...-e4be0ee Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

type APIError struct {
	Status int
	Body   string
}

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.

func (*APIError) Error

func (e *APIError) Error() string

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

func NewClient(cfg Config) (*Client, error)

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

func (c *Client) AuthorizeURL() string

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

func (c *Client) ClaimableInstallations(ctx context.Context, code string) ([]Installation, error)

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

func (c *Client) GetCommit(ctx context.Context, token, owner, repo, ref string) (Commit, error)

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 (c *Client) GetFileContents(ctx context.Context, token, owner, repo, path, ref string) (FileContents, error)

func (*Client) GetInstallation

func (c *Client) GetInstallation(ctx context.Context, installationID int64) (Installation, error)

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

func (c *Client) InstallURL() string

InstallURL is where a workspace admin installs the app (and grants repos).

func (*Client) InstallVerificationConfigured

func (c *Client) InstallVerificationConfigured() bool

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

func (c *Client) ListRepos(ctx context.Context, installationID int64) ([]Repo, error)

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

func (c *Client) RepoAccessible(ctx context.Context, token, owner, repo string) (bool, error)

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) Slug

func (c *Client) Slug() string

Slug is the configured app slug (used to build install URLs).

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

type RepoTreeEntry struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

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

type RuntimeDetection struct {
	Runtime         string
	MatchedManifest string
}

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

func (s *Service) Disconnect(ctx context.Context, ownerID string, installationID int64) error

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

func (s *Service) GetConnection(ctx context.Context, ownerID string) (Connection, error)

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

func (s *Service) GraphQLMutation() graphql.Fields

GraphQLMutation returns connectGit (returns the connection + install URL), claimGit (the ADR078 §3a claim flow for already-installed accounts), and disconnectGit.

func (*Service) GraphQLQuery

func (s *Service) GraphQLQuery() graphql.Fields

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

func (s *Service) ListBranches(ctx context.Context, ownerID, repoURL string) ([]string, error)

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

func (s *Service) ListConnections(ctx context.Context, ownerID string) ([]Connection, error)

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

func (s *Service) ListRepos(ctx context.Context, ownerID string) ([]Repo, error)

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

func (s *Service) RegisterMCP(srv *mcp.Server)

RegisterMCP adds the git-connect tools to the shared MCP server.

func (*Service) RegisterREST

func (s *Service) RegisterREST(mux *http.ServeMux)

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

func (s *Service) StartConnect(ctx context.Context, ownerID string) (Connection, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL