Documentation
¶
Index ¶
- Variables
- func AggregateGraphQLQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
- func AggregateQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
- func AggregateSearchQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
- func StartPoolRefresh(ctx context.Context, pool *TokenPool, refreshFn RefreshFunc, ...) func()
- type ArchiveHints
- type Client
- type DroppedTokenObserver
- type InstallationClient
- func (c *InstallationClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
- func (c *InstallationClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
- func (c *InstallationClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)
- func (c *InstallationClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)
- func (c *InstallationClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
- type InvalidationEvent
- type PATClient
- func (c *PATClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
- func (c *PATClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
- func (c *PATClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)
- func (c *PATClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)
- func (c *PATClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
- type PoolClient
- func (c *PoolClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
- func (c *PoolClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
- func (c *PoolClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)
- func (c *PoolClient) InvalidateTokens(tokens []string)
- func (c *PoolClient) IsOrgMember(ctx context.Context, org, username string) (bool, error)
- func (c *PoolClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
- func (c *PoolClient) Pool() *TokenPool
- type PoolEntry
- type RefreshFunc
- type Repo
- type SecurityAdvisoryCredit
- type TokenPool
- func (p *TokenPool) ActiveCount() int
- func (p *TokenPool) CheckQuotas(ctx context.Context) []TokenQuota
- func (p *TokenPool) EarliestReset() (earliest time.Time)
- func (p *TokenPool) Exhaust(token string, resetAt time.Time, family string)
- func (p *TokenPool) InvalidateAuth(token string)
- func (p *TokenPool) Labels() []string
- func (p *TokenPool) NeedsRefresh() bool
- func (p *TokenPool) RecentInvalidations(since time.Time) []InvalidationEvent
- func (p *TokenPool) Replace(entries []PoolEntry) []string
- func (p *TokenPool) SetRefreshCh(ch chan<- struct{})
- func (p *TokenPool) SignalRefresh()
- func (p *TokenPool) Size() int
- func (p *TokenPool) Token() string
- func (p *TokenPool) UsageCounts() []int
- type TokenQuota
- type UserProfile
Constants ¶
This section is empty.
Variables ¶
var ErrNoTokens = errors.New("no available GitHub tokens")
ErrNoTokens reports that every token in the pool is exhausted or the pool is empty. Callers pacing background work use it to back off until the pool recovers rather than retrying into a guaranteed failure.
Functions ¶
func AggregateGraphQLQuota ¶
func AggregateGraphQLQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
AggregateGraphQLQuota mirrors AggregateQuota for the GraphQL rate-limit family. Used by enrichment paths that fetch GHSA security credits.
func AggregateQuota ¶
func AggregateQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
AggregateQuota returns the aggregate remaining percentage and earliest reset time across all tokens, for the core (REST) rate-limit family. Returns 100 if quotas is empty or all errored.
func AggregateSearchQuota ¶
func AggregateSearchQuota(quotas []TokenQuota) (pctRemaining int, earliestReset time.Time)
AggregateSearchQuota mirrors AggregateQuota for the search-API rate-limit family. The scoring path issues three search calls per contributor (merged/closed/recent PRs) so the search quota is exhausted long before the core quota under load.
func StartPoolRefresh ¶
func StartPoolRefresh(ctx context.Context, pool *TokenPool, refreshFn RefreshFunc, notifyCh <-chan struct{}, tickInterval time.Duration, onDropped DroppedTokenObserver) func()
StartPoolRefresh runs a background goroutine that refreshes the token pool when tokens are near expiry or when signaled via notifyCh. Pass tickInterval=0 to use the default (1 minute). Returns a stop function that cancels the goroutine and waits for it to exit.
Types ¶
type ArchiveHints ¶
ArchiveHints provides pre-computed signals from GH Archive data, allowing fetchSignals to skip redundant GitHub Search API calls. When nil, all signals are fetched from the GitHub API. When Trusted is true, hints replace API calls. Otherwise the Search API is attempted first and hints serve as a fallback when API calls fail.
type Client ¶
type Client interface {
FetchUser(ctx context.Context, username string) (*UserProfile, error)
FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
IsOrgMember(ctx context.Context, org, username string) (bool, error)
// ListUserRepos returns up to maxRepos owned repositories. Caller is
// responsible for any forks/archived filtering. The order is determined
// by the GitHub API (currently "pushed" descending). maxRepos is
// clamped to a sane upper bound by the implementation.
ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
// FetchSecurityCredits returns the contributor's GHSA security
// advisory credits — published advisories where the user is credited
// as reporter, fixer, analyst, or other role. Uses the GitHub
// GraphQL API since the REST surface does not expose the per-user
// credits connection. Empty slice is a valid result (user has no
// public credits); error indicates fetch failure.
FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
}
Client abstracts GitHub API access.
type DroppedTokenObserver ¶
type DroppedTokenObserver func(tokens []string)
DroppedTokenObserver is called with the set of tokens dropped from the pool on each refresh, so per-token caches (e.g., PoolClient's *gh.Client memoization) can be invalidated. Optional — nil is safe.
type InstallationClient ¶
type InstallationClient struct {
// contains filtered or unexported fields
}
InstallationClient implements Client using GitHub App installation tokens. Tokens are cached and auto-refreshed before expiry. Mint operations are serialized through a separate mint mutex so concurrent requesters block at most one minter — the data mutex (RWMutex) stays free for readers.
func NewInstallationClient ¶
func NewInstallationClient(cfg *tenant.GitHubAppConfig, installationID int64) *InstallationClient
NewInstallationClient returns a Client backed by auto-refreshing installation tokens.
func (*InstallationClient) FetchSecurityCredits ¶
func (c *InstallationClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
FetchSecurityCredits queries the contributor's GHSA credits via GraphQL.
func (*InstallationClient) FetchSignals ¶
func (c *InstallationClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
FetchSignals retrieves scoring signals for the given user, optionally scoped to a repo.
func (*InstallationClient) FetchUser ¶
func (c *InstallationClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)
FetchUser retrieves a GitHub user profile.
func (*InstallationClient) IsOrgMember ¶
func (*InstallationClient) ListUserRepos ¶
func (c *InstallationClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
ListUserRepos retrieves the contributor's owned repositories.
type InvalidationEvent ¶
InvalidationEvent records that a token was 401'd and removed from rotation. Exposed via RecentInvalidations for the admin dashboard.
type PATClient ¶
type PATClient struct {
// contains filtered or unexported fields
}
PATClient implements Client using a personal access token.
func NewPATClient ¶
NewPATClient returns a Client backed by a personal access token. The ctx parameter is accepted for API consistency but is not stored; context.Background() is used for the oauth2 HTTP client because the returned PATClient outlives any single request context.
func (*PATClient) FetchSecurityCredits ¶
func (c *PATClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
FetchSecurityCredits queries the contributor's GHSA credits via GraphQL.
func (*PATClient) FetchSignals ¶
func (c *PATClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
FetchSignals retrieves scoring signals for the given user, optionally scoped to a repo.
func (*PATClient) IsOrgMember ¶
IsOrgMember checks if the user is a member of the given org.
type PoolClient ¶
type PoolClient struct {
// contains filtered or unexported fields
}
PoolClient implements Client using a TokenPool for round-robin token rotation with bounded retry on rate-limit errors. Each token rotation honors the actual reset time GitHub returns; the retry is backed by a generic helper so all five Client methods share the same loop.
func NewPoolClient ¶
func NewPoolClient(pool *TokenPool) *PoolClient
NewPoolClient returns a Client backed by the given token pool.
func (*PoolClient) FetchSecurityCredits ¶
func (c *PoolClient) FetchSecurityCredits(ctx context.Context, username string, maxCredits int) ([]SecurityAdvisoryCredit, error)
FetchSecurityCredits queries the contributor's GHSA credits via GraphQL, with automatic token rotation on rate limit.
func (*PoolClient) FetchSignals ¶
func (c *PoolClient) FetchSignals(ctx context.Context, username, repo string, hints *ArchiveHints) (*score.InputSignals, error)
FetchSignals retrieves scoring signals with automatic token rotation on rate limit.
func (*PoolClient) FetchUser ¶
func (c *PoolClient) FetchUser(ctx context.Context, username string) (*UserProfile, error)
FetchUser retrieves a GitHub user profile.
func (*PoolClient) InvalidateTokens ¶
func (c *PoolClient) InvalidateTokens(tokens []string)
InvalidateTokens drops cached *gh.Client entries for the given tokens. Called when the pool replaces entries (e.g., installation tokens re-minted) so we don't leak references to no-longer-active tokens. Safe to call with tokens that were never cached.
func (*PoolClient) IsOrgMember ¶
IsOrgMember checks if the user is a member of the given org with token rotation.
func (*PoolClient) ListUserRepos ¶
func (c *PoolClient) ListUserRepos(ctx context.Context, username string, maxRepos int) ([]Repo, error)
ListUserRepos retrieves the contributor's owned repositories with automatic token rotation on rate limit.
func (*PoolClient) Pool ¶
func (c *PoolClient) Pool() *TokenPool
Pool returns the underlying token pool for operational visibility.
type PoolEntry ¶
type PoolEntry struct {
InstallationID int64
Label string // target login (org/user) or "PAT"
Token string
ExpiresAt time.Time // zero means never expires (PAT)
}
PoolEntry describes a token source for the pool.
type RefreshFunc ¶
RefreshFunc loads current installations and returns pool entries.
type Repo ¶
type Repo struct {
Name string
FullName string
Description string
Language string
Stars int
Fork bool
Archived bool
PushedAt time.Time
}
Repo holds a subset of GitHub repository metadata used by enrichment aggregation. Only fields actually consumed by the aggregator are included; extending this struct is cheap.
type SecurityAdvisoryCredit ¶
type SecurityAdvisoryCredit struct {
AdvisoryID string // GHSA identifier, e.g. "GHSA-xxxx-yyyy-zzzz"
CreditType string // reporter / fixer / analyst / remediation_developer / etc.
Severity string // critical / high / moderate / low / unknown (lowercased)
CVEID string // CVE identifier when assigned, empty otherwise
Summary string // short advisory summary
PublishedAt time.Time // advisory publication date
}
SecurityAdvisoryCredit represents one GHSA credit edge: a contributor is credited as `CreditType` on advisory `AdvisoryID`. Fields mirror the subset of the GraphQL `SecurityAdvisoryCredit` and embedded `SecurityAdvisory` types that we surface in enrichment.
type TokenPool ¶
type TokenPool struct {
// contains filtered or unexported fields
}
TokenPool manages a pool of GitHub API tokens using round-robin selection. Thread-safe. Supports marking tokens as exhausted after rate limit errors; each exhaustion records the actual reset time GitHub returned (or a fallback) so the token only re-enters rotation when it is genuinely usable. Adapted from DevPulse pkg/data/ghutil/tokenpool.go.
func NewTokenPool ¶
NewTokenPool creates a pool from one or more tokens. Tokens can be passed individually or as a single comma-separated string.
func NewTokenPoolFromEntries ¶
NewTokenPoolFromEntries creates a pool from typed entries with metadata.
func (*TokenPool) ActiveCount ¶
ActiveCount returns the number of tokens that are usable (not exhausted and not expired).
func (*TokenPool) CheckQuotas ¶
func (p *TokenPool) CheckQuotas(ctx context.Context) []TokenQuota
CheckQuotas calls the GitHub rate_limit API for each token in the pool. This endpoint is free (does not count against quota).
func (*TokenPool) EarliestReset ¶
EarliestReset returns the soonest time an exhausted token re-enters rotation, or the zero time when nothing is currently exhausted. Pairs with ActiveCount so background work can sleep exactly until capacity returns instead of retrying into a dry pool.
func (*TokenPool) Exhaust ¶
Exhaust marks the given token as exhausted so Token() skips it until resetAt. A zero resetAt falls back to tokenResetFallback from now; a resetAt in the past is treated as the fallback (defensive against clock skew between this host and GitHub). The family parameter records which rate-limit family triggered the exhaustion (core/search/graphql/abuse/ unknown) so log-based metrics and alerts can distinguish routine search rotation from real REST exhaustion.
func (*TokenPool) InvalidateAuth ¶
InvalidateAuth marks the given token as exhausted in response to a 401 Bad credentials response from GitHub. Distinct log/path from Exhaust so operators can distinguish revoked/rotated credentials from rate-limit backoff.
Installation tokens (installationID != 0) get tokenResetFallback so they re-enter rotation after roughly the window an installation token would naturally expire — by then the refresh goroutine will have re-minted them. PAT entries (installationID == 0) get a far-future until so they are permanently disabled until the process restarts: a PAT cannot be re-minted and retrying a revoked one only burns latency.
func (*TokenPool) NeedsRefresh ¶
NeedsRefresh returns true if any installation token is within the expiry buffer.
func (*TokenPool) RecentInvalidations ¶
func (p *TokenPool) RecentInvalidations(since time.Time) []InvalidationEvent
RecentInvalidations returns events newer than since, oldest first. Pass a zero time to receive every event still in the ring. Safe for the admin dashboard to call without coordinating with the refresh loop.
func (*TokenPool) Replace ¶
Replace atomically swaps the pool entries. Resets cursor, counts, and exhaustion state. Returns the set of token strings that were dropped so callers can invalidate any per-token caches (e.g., PoolClient's *gh.Client memoization). The returned slice contains only tokens that no longer appear in the new entries — tokens that survived the swap stay valid.
func (*TokenPool) SetRefreshCh ¶
func (p *TokenPool) SetRefreshCh(ch chan<- struct{})
SetRefreshCh sets a channel that Token() will signal (non-blocking) when it selects a near-expiry token, allowing the refresh goroutine to re-mint early.
func (*TokenPool) SignalRefresh ¶
func (p *TokenPool) SignalRefresh()
SignalRefresh nudges the refresh goroutine to re-mint installation tokens, if a refresh channel has been registered. Non-blocking: if the channel already has a pending signal, this is a no-op. Used by callers that detect a poisoned token (e.g., auth failure) and want fresh credentials minted ahead of the periodic tick.
func (*TokenPool) Token ¶
Token returns the next non-exhausted token in the round-robin rotation. Returns "" when all tokens are exhausted or the pool is empty. Entries with a non-zero ExpiresAt that falls within tokenExpiryBuffer are skipped.
func (*TokenPool) UsageCounts ¶
UsageCounts returns a copy of per-token call counts (indexed by pool position).
type TokenQuota ¶
type TokenQuota struct {
Index int
Label string
InstallationID int64
Limit int
Remaining int
Reset time.Time
SearchLimit int
SearchRemaining int
SearchReset time.Time
GraphQLLimit int
GraphQLRemaining int
GraphQLReset time.Time
Error string
}
TokenQuota holds rate limit info for a single GitHub API token. Limit / Remaining / Reset describe the core (REST) family; the Search* and GraphQL* fields cover the secondary families that the scoring path actually burns through (3 search calls per signal fetch, GraphQL for security credits). Empty values for a family mean GitHub didn't return it (rare; older endpoints).