Forge Client¶
internal/forge is a small, read-only GitHub REST client. Two features use it: min_release_age reads the forge's publish time of a tag, and approvals read pull request reviews, CODEOWNERS and team membership. It is a library, not a command; nothing in it writes to a forge.
What it reads¶
| Method | GitHub endpoint | Used for |
|---|---|---|
Releases(repo) |
GET /repos/{o}/{r}/releases |
publish dates of all releases (drafts dropped) |
Release(repo, tag) |
GET /repos/{o}/{r}/releases/tags/{tag} |
publish date of one tag |
Tag(repo, tag) |
GET /git/ref/tags/{tag}, GET /git/tags/{sha} |
peeled commit, annotated tag object, tagger date |
CommitDate(repo, sha) |
GET /commits/{sha} |
committer date (forgeable) |
PullRequestsForCommit(repo, sha) |
GET /commits/{sha}/pulls |
the PR a merged commit came from |
PullRequest(repo, n) |
GET /pulls/{n} |
the pull request's author and current head commit |
Reviews(repo, n) |
GET /pulls/{n}/reviews |
who approved, at which head commit, and the reviewer's author_association |
Codeowners(repo, ref) |
GET /contents/{path}?ref= |
.github/CODEOWNERS, then CODEOWNERS, then docs/CODEOWNERS |
TeamMembers(team), IsTeamMember(team, login) |
GET /orgs/{org}/teams/{slug}/members, .../memberships/{login} |
expanding @org/team owners |
CollaboratorPermission(repo, login) |
GET /repos/{o}/{r}/collaborators/{login}/permission |
requiring a write, maintain or admin role for review approvals; needs a token with push access (ErrForbidden otherwise) |
ParseRepo turns a source (https://, git+https://, ssh://, git@host:o/r) into a Repo. Plain http, git://, file://, local paths and nested paths are ErrUnsupportedSource: a repository whose identity is uncertain is never looked up.
Safety properties¶
- Token. Only from the environment:
GITHUB_TOKEN, thenGH_TOKEN, thengh auth token --hostname <host>(fixed argv, started through the injected runner, no credential variable passed). Never from a config file or a flag, never logged, never in an error. With no token the client is anonymous, which is enough for release and commit dates of public repositories. - Hosts. A repository is contacted only when its host is on the forge allowlist (
AI_RULEZ_FORGE_HOSTS, comma separated, environment only, defaultgithub.com), over https. It is separate fromAI_RULEZ_GIT_TOKEN_HOSTS, so a GitLab host trusted with a clone token never receives the GitHub token. Any other host isErrHostNotAllowedand gets no request, with or without a token. GitHub Enterprise Server ishttps://<host>/api/v3once its host is inAI_RULEZ_FORGE_HOSTS. - Redirects. Followed only to https on the same host (at most 3); a pagination link that leaves the API host is refused. The token cannot follow a redirect elsewhere.
- Caps. A response body is capped at 4 MiB (CODEOWNERS 3 MiB, GitHub's own limit): larger is
ErrTooLarge. A listing reads at most 10 pages of 100; the items read so far are returned withErrTruncated. - Input. Owner, repository, team, login, tag, ref and commit id are validated before they become part of a URL.
- Offline. With
Options.Offlineevery method returnsErrOfflinewithout a request.
Errors¶
ErrNotFound, ErrUnauthorized (bad token, or a lookup that needs one), ErrForbidden (token lacks a permission, e.g. read:org for teams), ErrRateLimited (*RateLimitError carries the reset time), ErrTruncated, ErrTooLarge, ErrOffline, ErrHostNotAllowed, ErrUnsupportedSource. Test with errors.Is.
Notes for approvals¶
The interface is shaped for approval checks (internal/approval):
- A review counts for content only when the commit it was made on (
Review.CommitID) is the pull request's final head (PullRequest) and the content there has the digest being approved (review-linked approvals). - The latest review of each reviewer decides: a later
CHANGES_REQUESTEDorDISMISSEDreplaces an earlierAPPROVED.COMMENTEDandPENDINGnever approve. Reviews,PullRequestsForCommitandTeamMemberscan returnErrTruncatedwith a partial list. Count approvals only from a complete list; treat a truncated one as no approval.IsTeamMemberreturnsfalse, not an error, for a non-member. Team calls need a token withread:org; without it they fail withErrUnauthorizedorErrForbidden, and an approval check must fail closed.Codeownersreturns the raw file; matching owners to paths is the caller's job. An owner may be@user,@org/teamor an email.
Testing¶
forge.Fake is an in-memory Client (a missing entry is ErrNotFound, Err fails every call, Calls() records them). forgetest.New(t) starts a local TLS server that answers the endpoints above from plain Go data, with knobs for pages, forced statuses, rate limits, redirects, oversized bodies and bad pagination links; srv.Client(env) returns an HTTPClient wired to it that never starts a process. Tests never call a real forge. A live check, if one is added, belongs behind AI_RULEZ_LIVE_FORGE=1.