Documentation
¶
Overview ¶
Package webhook handles GitHub webhook events for the Chetter service.
Package webhook handles GitHub webhook events for the Chetter service.
Package webhook handles GitHub webhook events for the Chetter service. It verifies webhook signatures, parses events, and submits review tasks to the chetter service.
Package webhook handles GitHub webhook events for the Chetter service.
Package webhook handles GitHub webhook events for the Chetter service.
Index ¶
- Constants
- func TriggerMatchesLabels(triggerLabels, issueLabels []string) bool
- type ArtifactRecorder
- type AuditEventParams
- type AuditLogger
- type CheckRunSummary
- type Client
- func (c *Client) AddIssueLabel(ctx context.Context, repo string, prNumber int, label string) error
- func (c *Client) CheckUserHasWriteAccess(ctx context.Context, repo, username string) (bool, error)
- func (c *Client) CreateBranch(ctx context.Context, repo, branch, sha string) error
- func (c *Client) CreateIssue(ctx context.Context, repo, title, body string, labels []string) (CreatedGitHubArtifact, error)
- func (c *Client) CreateIssueComment(ctx context.Context, repo string, prNumber int, body string) error
- func (c *Client) CreateIssueCommentWithResponse(ctx context.Context, repo string, issueNumber int, body string) (CreatedGitHubArtifact, error)
- func (c *Client) CreatePullRequest(ctx context.Context, repo, title, body, head, base string, draft bool) (CreatedGitHubArtifact, error)
- func (c *Client) CreatePullRequestReview(ctx context.Context, repo string, prNumber int, event, body string) (CreatedGitHubArtifact, error)
- func (c *Client) CredentialForRepo(ctx context.Context, repo string, profile PermissionProfile) (Credential, error)
- func (c *Client) GetAppLogin(ctx context.Context) (string, error)
- func (c *Client) GetBranchSHA(ctx context.Context, repo, branch string) (string, error)
- func (c *Client) GetIssueDetails(ctx context.Context, repo string, issueNumber int) (IssueDetails, error)
- func (c *Client) GetPullRequest(ctx context.Context, repo string, prNumber int) (headRef, baseRef, cloneURL string, err error)
- func (c *Client) GetPullRequestDetails(ctx context.Context, repo string, prNumber int) (PullRequestDetails, error)
- func (c *Client) HasLabel(ctx context.Context, repo string, prNumber int, label string) (bool, error)
- func (c *Client) ListCheckRunsForRef(ctx context.Context, repo, ref string) (CheckRunSummary, error)
- func (c *Client) ListPRFiles(ctx context.Context, repo string, prNumber int) ([]string, error)
- func (c *Client) UpsertFile(ctx context.Context, repo, branch, path, content, message string) error
- type Comment
- type CreatedGitHubArtifact
- type Credential
- type DeliveryStore
- type Handler
- type HandlerConfig
- type Installation
- type Issue
- type IssueCommentEvent
- type IssueData
- type IssueDetails
- type IssueEvent
- type Label
- type Manager
- func (m *Manager) AppLogin(ctx context.Context) (string, error)
- func (m *Manager) ClientForInstallation(ctx context.Context, installationID int64) (*Client, error)
- func (m *Manager) ClientForRepo(ctx context.Context, repo string) (*Client, error)
- func (m *Manager) CredentialForRepo(ctx context.Context, repo string, profile PermissionProfile) (Credential, error)
- func (m *Manager) LegacyClient() *Client
- type ManagerOption
- type PRBranch
- type PermissionProfile
- type PullRequest
- type PullRequestDetails
- type PullRequestEvent
- type PullRequestReviewCommentEvent
- type PullRequestReviewEvent
- type RecentDeliveries
- type RecordArtifactParams
- type RecordDeliveryParams
- type Repository
- type Review
- type ReviewContext
- type ReviewTrigger
- type SessionResumer
- type SubmitTaskRequest
- type TaskSubmitter
- type TaskSubmitterService
- type TriggerResolver
Constants ¶
const ( // Action values for the pull_request event. PullRequestActionOpened = "opened" PullRequestActionSynchronize = "synchronize" PullRequestActionReopened = "reopened" PullRequestActionLabeled = "labeled" // EventType values for the X-GitHub-Event header. EventTypePullRequest = "pull_request" EventTypeIssueComment = "issue_comment" EventTypeIssues = "issues" EventTypePullRequestReview = "pull_request_review" EventTypePullRequestReviewComment = "pull_request_review_comment" // ChetterReviewLabel is the label we add to PRs that should be reviewed. ChetterReviewLabel = "chetter-review" // ReviewTrigger comment that users post to request a review. ReviewTriggerCommand = "/chetter-review" // Trigger event values for trigger_config's "event" field. TriggerEventOpened = "opened" TriggerEventLabeled = "labeled" TriggerEventComment = "comment" TriggerEventFork = "fork" TriggerEventCreated = "created" // issue created TriggerEventSynchronize = "synchronize" )
const CommentReviewFailed = "🤖 Chetter review could not start. Please check the chetter service logs."
CommentReviewFailed is posted on a PR when Chetter fails to start a review.
Variables ¶
This section is empty.
Functions ¶
func TriggerMatchesLabels ¶
TriggerMatchesLabels checks if any of the issue's labels match the trigger's required labels. If the trigger has no match_labels, all issues match.
Types ¶
type ArtifactRecorder ¶
type ArtifactRecorder interface {
RecordArtifact(ctx context.Context, params RecordArtifactParams) error
}
ArtifactRecorder is the interface for recording task artifacts discovered from webhook events (issues, PRs, comments with Chetter footer signatures).
type AuditEventParams ¶
type AuditEventParams struct {
EventType string
SourceType string
SourceID string
TargetType string
TargetID string
Repo string
GitHubEvent string
GitHubAction string
GitHubDeliveryID string
ParentEventID string
Detail string
Payload json.RawMessage
}
AuditEventParams holds the data for a single audit log entry.
type AuditLogger ¶
type AuditLogger interface {
LogAuditEvent(ctx context.Context, params AuditEventParams) error
}
AuditLogger is the interface for recording server-side audit events.
type CheckRunSummary ¶
type Client ¶
type Client struct {
InstallationID int64
// contains filtered or unexported fields
}
Client wraps the GitHub API for one immutable installation.
func (*Client) AddIssueLabel ¶
AddIssueLabel adds a label to a PR (issues and PRs share the labels API).
func (*Client) CheckUserHasWriteAccess ¶
CheckUserHasWriteAccess returns true if the given user has write or admin permission on the repo. Used to gate the /chetter-review comment trigger.
func (*Client) CreateBranch ¶
func (*Client) CreateIssue ¶
func (*Client) CreateIssueComment ¶
func (c *Client) CreateIssueComment(ctx context.Context, repo string, prNumber int, body string) error
CreateIssueComment posts a comment on a PR.
func (*Client) CreateIssueCommentWithResponse ¶
func (*Client) CreatePullRequest ¶
func (*Client) CreatePullRequestReview ¶
func (*Client) CredentialForRepo ¶
func (c *Client) CredentialForRepo(ctx context.Context, repo string, profile PermissionProfile) (Credential, error)
CredentialForRepo returns a repository-restricted credential from this client's immutable installation.
func (*Client) GetAppLogin ¶
GetAppLogin is retained for compatibility; App login is manager-scoped.
func (*Client) GetBranchSHA ¶
func (*Client) GetIssueDetails ¶
func (c *Client) GetIssueDetails(ctx context.Context, repo string, issueNumber int) (IssueDetails, error)
GetIssueDetails fetches the authoritative metadata for a GitHub issue.
func (*Client) GetPullRequest ¶
func (c *Client) GetPullRequest(ctx context.Context, repo string, prNumber int) (headRef, baseRef, cloneURL string, err error)
GetPullRequest fetches a pull request and returns the head ref, base ref, and clone URL of the head repository.
func (*Client) GetPullRequestDetails ¶
func (*Client) HasLabel ¶
func (c *Client) HasLabel(ctx context.Context, repo string, prNumber int, label string) (bool, error)
HasLabel reports whether the label is already on the PR.
func (*Client) ListCheckRunsForRef ¶
func (*Client) ListPRFiles ¶
ListPRFiles returns the list of filenames changed in a pull request.
type Comment ¶
type Comment struct {
Body string `json:"body"`
User struct {
Login string `json:"login"`
} `json:"user"`
}
Comment is the issue/PR comment object.
type CreatedGitHubArtifact ¶
type Credential ¶
Credential is a short-lived GitHub installation credential. Callers must not persist or log Token.
type DeliveryStore ¶
type DeliveryStore interface {
// RecordDelivery inserts a delivery row. Returns false (with nil error)
// if the delivery_id was already recorded (idempotent dedup).
RecordDelivery(ctx context.Context, params RecordDeliveryParams) (created bool, err error)
// MarkDeliveryProcessing marks a delivery as currently being handled.
MarkDeliveryProcessing(ctx context.Context, deliveryID string) error
// MarkDeliveryCompleted marks a delivery as successfully processed.
MarkDeliveryCompleted(ctx context.Context, deliveryID string) error
// MarkDeliveryFailed marks a delivery as failed with the error and
// schedules the next retry (or dead-letters if attempts exhausted).
MarkDeliveryFailed(ctx context.Context, deliveryID string, errMsg string) error
}
DeliveryStore persists webhook deliveries for idempotency, retry, and delivery status tracking. When non-nil, the handler records every delivery before processing and updates its status on completion or failure. A background worker (in the service layer) retries failed deliveries with exponential backoff and dead-letters them after max_attempts. See issue #102.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler serves GitHub webhook events. Implements http.Handler.
func NewHandler ¶
func NewHandler(cfg HandlerConfig, github *Manager, submitter TaskSubmitter, triggers TriggerResolver, audit AuditLogger, artifacts ArtifactRecorder, resumer SessionResumer, deliveryStore DeliveryStore) *Handler
NewHandler creates a webhook Handler. If the configuration is incomplete, the returned handler will accept requests but log "webhook disabled" for every event (kill switch behavior).
func (*Handler) ProcessDelivery ¶
ProcessDelivery is the exported entry point for the retry worker to re-process a failed delivery. It runs the handler synchronously and updates the delivery store with the outcome. See issue #102.
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP handles an incoming GitHub webhook request.
func (*Handler) Shutdown ¶
Shutdown waits for in-flight webhook processing goroutines to finish, or until ctx expires. It must be called after the HTTP server has stopped accepting new requests but before the database is closed, so that in-flight events can complete their DB operations. If the deadline expires, remaining goroutines are abandoned (they continue running but the server proceeds with shutdown); the event is logged. See issue #57.
type HandlerConfig ¶
HandlerConfig is the configuration for the webhook handler.
type Installation ¶
type Installation struct {
ID int64 `json:"id"`
}
Installation identifies the GitHub App installation that signed event work must use for installation-authenticated API calls.
type Issue ¶
type Issue struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body"`
HTMLURL string `json:"html_url"`
PullRequest *struct {
URL string `json:"url"`
} `json:"pull_request,omitempty"`
Labels []Label `json:"labels"`
}
Issue is the issue/PR object (PRs come through the issues API).
type IssueCommentEvent ¶
type IssueCommentEvent struct {
Action string `json:"action"`
Comment Comment `json:"comment"`
Issue Issue `json:"issue"`
Repository Repository `json:"repository"`
Installation Installation `json:"installation"`
}
IssueCommentEvent is the top-level payload for an issue_comment webhook event. For PR comments, the Issue object includes a `pull_request` field which we use to determine that this is a PR comment (vs an issue comment).
func (*IssueCommentEvent) IsPullRequest ¶
func (e *IssueCommentEvent) IsPullRequest() bool
IsPullRequest returns true if the issue is actually a pull request.
type IssueData ¶
type IssueData struct {
Number int `json:"number"`
Title string `json:"title"`
State string `json:"state"`
Body string `json:"body"`
HTMLURL string `json:"html_url"`
User struct {
Login string `json:"login"`
} `json:"user"`
Labels []Label `json:"labels"`
}
IssueData is the relevant subset of the issue object.
type IssueDetails ¶
type IssueDetails struct {
Number int
State string
Title string
Body string
HTMLURL string
Labels []string
}
IssueDetails is the authoritative issue metadata used by the manual trigger test flow. It is fetched from GitHub so label matching and the default prompt never trust editable client-supplied fields.
type IssueEvent ¶
type IssueEvent struct {
Action string `json:"action"`
Issue IssueData `json:"issue"`
Label *Label `json:"label,omitempty"`
Repository Repository `json:"repository"`
Installation Installation `json:"installation"`
}
IssueEvent is the top-level payload for an issues webhook event.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns GitHub App credentials and installation-specific caches.
func NewManager ¶
func NewManager(appID int64, privateKeyPEMBase64 string, opts ...ManagerOption) (*Manager, error)
NewManager parses GitHub App credentials and creates a process-wide manager.
func (*Manager) ClientForInstallation ¶
ClientForInstallation returns an immutable client whose token cache is isolated from every other installation.
func (*Manager) ClientForRepo ¶
ClientForRepo discovers the App installation authorized for repo using an App JWT and caches the mapping for a bounded TTL.
func (*Manager) CredentialForRepo ¶
func (m *Manager) CredentialForRepo(ctx context.Context, repo string, profile PermissionProfile) (Credential, error)
CredentialForRepo discovers the repository installation and returns a repository-restricted credential for profile.
func (*Manager) LegacyClient ¶
LegacyClient returns the optional fallback installation client.
type ManagerOption ¶
ManagerOption customizes a GitHub App manager.
func WithAPIBaseURL ¶
func WithAPIBaseURL(baseURL string) ManagerOption
WithAPIBaseURL overrides the GitHub API base URL. It is intended for tests and GitHub Enterprise-compatible API endpoints.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) ManagerOption
WithHTTPClient overrides the HTTP client used by the manager.
func WithLegacyInstallationID ¶
func WithLegacyInstallationID(installationID int64) ManagerOption
WithLegacyInstallationID configures the optional installation used by repository-less legacy callers until they are migrated to ClientForRepo.
type PRBranch ¶
type PRBranch struct {
Ref string `json:"ref"`
SHA string `json:"sha"`
Repo struct {
FullName string `json:"full_name"`
CloneURL string `json:"clone_url"`
} `json:"repo"`
}
PRBranch is the head or base ref of a pull request.
type PermissionProfile ¶
type PermissionProfile string
PermissionProfile identifies the least-privilege permission set requested for a repository-restricted installation credential.
const (
PermissionProfileTaskGit PermissionProfile = "task-git"
)
type PullRequest ¶
type PullRequest struct {
Number int `json:"number"`
State string `json:"state"`
Title string `json:"title"`
Body string `json:"body"`
Head PRBranch `json:"head"`
Base PRBranch `json:"base"`
User struct {
Login string `json:"login"`
} `json:"user"`
Labels []Label `json:"labels"`
}
PullRequest is the relevant subset of the pull_request object.
type PullRequestDetails ¶
type PullRequestEvent ¶
type PullRequestEvent struct {
Action string `json:"action"`
Number int `json:"number"`
PullRequest PullRequest `json:"pull_request"`
Label *Label `json:"label,omitempty"`
Repository Repository `json:"repository"`
Installation Installation `json:"installation"`
Sender struct {
Login string `json:"login"`
} `json:"sender"`
}
PullRequestEvent is the top-level payload for a pull_request webhook event.
type PullRequestReviewCommentEvent ¶
type PullRequestReviewCommentEvent struct {
Action string `json:"action"`
PullRequest PullRequest `json:"pull_request"`
Comment Comment `json:"comment"`
Repository Repository `json:"repository"`
Installation Installation `json:"installation"`
}
PullRequestReviewCommentEvent is the top-level payload for pull_request_review_comment.
type PullRequestReviewEvent ¶
type PullRequestReviewEvent struct {
Action string `json:"action"`
PullRequest PullRequest `json:"pull_request"`
Review Review `json:"review"`
Repository Repository `json:"repository"`
Installation Installation `json:"installation"`
}
PullRequestReviewEvent is the top-level payload for pull_request_review.
type RecentDeliveries ¶
type RecentDeliveries struct {
// contains filtered or unexported fields
}
RecentDeliveries tracks recent X-GitHub-Delivery IDs to prevent duplicate processing of the same webhook delivery. Not persisted — if the process restarts mid-review, the review is simply lost (acceptable; GitHub will not redeliver on 2xx).
func NewRecentDeliveries ¶
func NewRecentDeliveries(ttl time.Duration, maxSize int) *RecentDeliveries
NewRecentDeliveries creates a tracker that holds IDs for ttl and caps the map at maxSize entries (evicting oldest by expiry).
func (*RecentDeliveries) Seen ¶
func (r *RecentDeliveries) Seen(id string) bool
Seen returns true if the delivery ID was already recorded (and not expired). As a side effect, it records the ID with the current time.
func (*RecentDeliveries) Size ¶
func (r *RecentDeliveries) Size() int
Size returns the current number of tracked IDs (for tests/debugging).
type RecordArtifactParams ¶
type RecordArtifactParams struct {
TaskID string
AgentSessionID string
UserPromptID string
ExecutionAttemptID string
ArtifactType string
Repo string
Number int
URL string
Ref string
SHA string
DiscoverySource string
}
RecordArtifactParams holds the data for a single task artifact entry.
type RecordDeliveryParams ¶
type RecordDeliveryParams struct {
DeliveryID string
EventType string
Action string
Payload []byte
}
RecordDeliveryParams holds the data for a single webhook delivery record.
type Repository ¶
type Repository struct {
FullName string `json:"full_name"`
Name string `json:"name"`
Owner struct {
Login string `json:"login"`
} `json:"owner"`
}
Repository is the relevant subset of the repository object.
type Review ¶
type Review struct {
User struct {
Login string `json:"login"`
} `json:"user"`
}
Review is the relevant subset of a pull request review object.
type ReviewContext ¶
type ReviewContext struct {
TeamID string
TriggerName string
TriggerType string
Trigger string // "label", "fork", "file-pattern", "comment"
Repo string // e.g., "chetter/chetter"
PRNumber int
BaseRef string
HeadRef string
HeadCloneURL string
CommentAuthor string // only set for comment triggers
GitHubInstallationID int64
Prompt string // trigger-supplied prompt; empty falls back to the built-in template
AgentImage string // trigger-supplied agent image; empty falls back to the default
Agent string // reviewer agent name (from the trigger config)
ProviderID string // reviewer provider ID (from the trigger config)
ModelID string // reviewer model ID (from the trigger config)
VariantID string // reviewer variant ID (from the trigger config)
Skills []string
TimeoutSec int // reviewer task timeout (from the trigger config)
SessionMode string
PauseReason string
TTLHours int
// Isolation marks review tasks as requiring enforced isolation (issue #291).
Isolation string
}
ReviewContext is the data passed to TaskSubmitter for a single review.
type ReviewTrigger ¶
type ReviewTrigger struct {
TeamID string
Name string
TriggerType string
Prompt string
AgentImage string
Agent string
ProviderID string
ModelID string
VariantID string
TimeoutSec int
GitURL string
GitRef string
Skills []string
Event string // which webhook action this trigger responds to (e.g. "opened", "labeled"), empty = all
MatchLabels []string // required issue labels; empty = all labels match
SessionMode string
PauseReason string
TTLHours int
// Isolation marks review tasks as requiring enforced isolation (issue #291).
Isolation string
}
ReviewTrigger is the resolved data from a single trigger.
type SessionResumer ¶
type SessionResumer interface {
ResumeSessionForPR(ctx context.Context, repo string, prNumber int) error
}
SessionResumer is the interface for resuming paused or recoverable agent sessions.
type SubmitTaskRequest ¶
type SubmitTaskRequest struct {
TeamID string
Prompt string
GitURL string
GitRef string
GitHubRepo string
GitHubInstallationID int64
AgentImage string
Agent string
ProviderID string
ModelID string
VariantID string
Skills []string
Env map[string]string
TimeoutSec int
TriggerName string
TriggerType string
SessionMode string
PauseReason string
TTLHours int
// Isolation marks the task as requiring enforced isolation (issue #291).
Isolation string
}
SubmitTaskRequest is a minimal copy of service.SubmitTaskRequest. We define it here to avoid importing the service package. The service adapter in main.go converts from service.SubmitTaskRequest to this type.
func BuildIssueTaskRequest ¶
func BuildIssueTaskRequest(t ReviewTrigger, repo string, installationID int64, prompt string, env map[string]string) SubmitTaskRequest
BuildIssueTaskRequest converts a resolved issue trigger into a task submission request, mirroring the dispatch used by the issues and issue_comment webhook handlers.
func BuildReviewTaskRequest ¶
func BuildReviewTaskRequest(review ReviewContext) SubmitTaskRequest
BuildReviewTaskRequest creates a SubmitTaskRequest for a PR review. It is the canonical conversion used by both the webhook dispatch path and the manual trigger test flow so the two never drift.
type TaskSubmitter ¶
type TaskSubmitter interface {
SubmitReviewTask(ctx context.Context, review ReviewContext) error
SubmitTask(ctx context.Context, req SubmitTaskRequest) (any, error)
}
TaskSubmitter is the subset of service.Service that the webhook needs to submit tasks. Defined as an interface to allow mocking in tests.
func NewServiceSubmitter ¶
func NewServiceSubmitter(svc TaskSubmitterService) TaskSubmitter
NewServiceSubmitter creates a TaskSubmitter that wraps the given service adapter. Use this in main.go to wire the webhook handler to the service.
type TaskSubmitterService ¶
type TaskSubmitterService interface {
// SubmitTask matches the signature of service.Service.SubmitTask. The
// return value is ignored by the webhook caller.
SubmitTask(ctx context.Context, req SubmitTaskRequest) (any, error)
}
TaskSubmitterService is the interface that the webhook package needs from the service package. It's defined in the webhook package so webhook doesn't need to import service. In main.go, an adapter is provided that satisfies this interface by calling the service's SubmitTask method.
type TriggerResolver ¶
type TriggerResolver interface {
ListEnabledPRReviewTriggersByRepo(ctx context.Context, repo string) ([]ReviewTrigger, error)
ListEnabledIssueTriggersByRepo(ctx context.Context, repo string) ([]ReviewTrigger, error)
}
TriggerResolver is the subset of the service that the webhook needs to resolve triggers for a given repo. Defined as an interface to allow mocking in tests.