Skip to content
293 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Stripe webhooks, enterprise invoices, and sudo for both1//! The webhooks service: events, delivered to the addresses a repository or
2//! a workspace registers.
3//!
4//! Every event g1t publishes can be delivered: an HTTPS `POST` of JSON,
5//! signed with the webhook's secret in `X-G1t-Signature-256`, and retried
6//! with growing waits when the receiver does not answer with a 2xx. Each
7//! delivery is kept, with what was sent and what came back, and can be sent
8//! again.
9//!
10//! Mirrors `packages/contracts/src/webhooks.ts`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Every event a webhook can be sent, in the order people are shown them.
@g1t welcomes you when you join a workspace, and that is the one introduction any agent makes. Identity now says when a person becomes a member, workspace.member_joined, by invitation, by an invite code, or by creating the workspace, and webhooks can hear it; the agents service hears it too, and for anyone who joined, never the creator, the workspace's @g1t opens your direct message and says in its own voice who it is, what to ask it, how agents answer, and where things are, once per person per workspace and charged to @g1t like any reply, with a fixed welcome when no model can be used. The agents, workspaces, authentication and webhooks guides say so.18pub const EVENT_TYPES: [&str; 106] = [
Stripe webhooks, enterprise invoices, and sudo for both19 "git.push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look20 "branch.renamed",
Stripe webhooks, enterprise invoices, and sudo for both21 "repo.created",
22 "repo.forked",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look23 "repo.updated",
24 "repo.visibility_changed",
25 "repo.renamed",
26 "repo.transferred",
27 "repo.default_branch_changed",
28 "repo.archived",
29 "repo.unarchived",
30 "repo.deleted",
31 "repo.restored",
32 "repo.purged",
33 "repo.collaborator_added",
34 "repo.collaborator_removed",
35 "repo.collaborator_role_changed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar36 "team.created",
37 "team.edited",
38 "team.deleted",
39 "team.member_added",
40 "team.member_role_changed",
41 "team.member_removed",
42 "team.repo_added",
43 "team.repo_role_changed",
44 "team.repo_removed",
@g1t welcomes you when you join a workspace, and that is the one introduction any agent makes. Identity now says when a person becomes a member, workspace.member_joined, by invitation, by an invite code, or by creating the workspace, and webhooks can hear it; the agents service hears it too, and for anyone who joined, never the creator, the workspace's @g1t opens your direct message and says in its own voice who it is, what to ask it, how agents answer, and where things are, once per person per workspace and charged to @g1t like any reply, with a fixed welcome when no model can be used. The agents, workspaces, authentication and webhooks guides say so.45 "workspace.member_joined",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge46 "ruleset.created",
47 "ruleset.updated",
48 "ruleset.deleted",
Stripe webhooks, enterprise invoices, and sudo for both49 "issue.opened",
50 "issue.updated",
51 "issue.assigned",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar52 "issue.labeled",
53 "issue.unlabeled",
54 "issue.milestoned",
55 "issue.demilestoned",
Stripe webhooks, enterprise invoices, and sudo for both56 "issue.closed",
57 "issue.reopened",
58 "comment.created",
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts59 "comment.edited",
60 "comment.deleted",
Stripe webhooks, enterprise invoices, and sudo for both61 "pull.opened",
62 "pull.ready",
63 "pull.updated",
64 "pull.merge_requested",
65 "pull.merged",
66 "pull.closed",
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts67 "pull.reopened",
68 "pull.converted_to_draft",
Events: review requests, assignments, stops and deployments are published69 "pull.assigned",
70 "pull.review_requested",
71 "pull.review_request_removed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar72 "pull.labeled",
73 "pull.unlabeled",
74 "pull.milestoned",
75 "pull.demilestoned",
76 "pull.base_changed",
Events: review requests, assignments, stops and deployments are published77 "pull.stalled",
78 "pull.resumed",
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts79 "release.created",
80 "release.published",
81 "release.released",
82 "release.prereleased",
83 "release.edited",
84 "release.unpublished",
85 "release.deleted",
Stripe webhooks, enterprise invoices, and sudo for both86 "agent.asked",
87 "checks.completed",
Merge checks: statuses and check runs on every commit88 "status.created",
89 "check_run.created",
90 "check_run.completed",
91 "check_run.rerequested",
92 "check_run.requested_action",
93 "check_suite.completed",
94 "check_suite.rerequested",
Stripe webhooks, enterprise invoices, and sudo for both95 "review.completed",
96 "workflow.completed",
Events: review requests, assignments, stops and deployments are published97 "deployment.succeeded",
98 "deployment.failed",
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9799 "deployment.created",
100 "deployment_status.created",
Stripe webhooks, enterprise invoices, and sudo for both101 "queue.changed",
102 "session.appended",
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member103 "package.published",
104 "package.version_deleted",
105 "package.deleted",
106 "package.visibility_changed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar107 "secret_scanning_alert.created",
108 "secret_scanning_alert.fixed",
109 "secret_scanning_alert.dismissed",
110 "secret_scanning_alert.reopened",
111 "secret_scanning.bypass_requested",
112 "secret_scanning.bypass_reviewed",
113 "code_scanning_alert.created",
114 "code_scanning_alert.fixed",
115 "code_scanning_alert.dismissed",
116 "code_scanning_alert.reopened",
117 "vulnerability_alert.created",
118 "vulnerability_alert.fixed",
119 "vulnerability_alert.dismissed",
120 "vulnerability_alert.reopened",
Merge branch 'mirroring' into artifacts-mode121 "mirror.unreachable",
122 "mirror.reachable",
123 "mirror.state_changed",
124 "mirror.moved_in",
Stripe webhooks, enterprise invoices, and sudo for both125];
126
127/// What a webhook belongs to.
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
129#[serde(rename_all = "snake_case")]
130pub enum HookScope {
131 /// One repository's events.
132 Repo,
133 /// The events of every repository in a workspace.
134 Workspace,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct Hook {
140 pub id: String,
141 pub scope: HookScope,
142 pub workspace: String,
143 /// For a repository's webhook: `owner/name`.
144 pub repo: Option<String>,
145 pub url: String,
146 /// The event types it is sent, or `["*"]` for all.
147 pub events: Vec<String>,
148 pub active: bool,
149 /// The last four characters of its secret.
150 pub secret_hint: String,
151 pub created_by: String,
152 /// RFC 3339.
153 pub created_at: String,
154 /// How its latest delivery went: `delivered`, `pending` or `failed`.
155 pub last_status: Option<String>,
156 pub last_delivered_at: Option<String>,
157}
158
159/// One event sent, or being sent, to a webhook.
160#[derive(Clone, Debug, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct HookDelivery {
163 pub id: String,
164 pub hook_id: String,
165 /// The event's id, or empty for a ping.
166 pub event_id: String,
167 pub event: String,
168 /// `pending` while it will be tried again, `delivered`, or `failed` once
169 /// it has been tried as often as it will be.
170 pub status: String,
171 pub attempts: u32,
172 /// The receiver's HTTP status, the last time it answered.
173 pub response_status: Option<u16>,
174 /// The start of what it answered.
175 pub response_body: Option<String>,
176 /// Why the last attempt failed, when the receiver could not be reached.
177 pub error: Option<String>,
178 pub duration_ms: Option<u32>,
179 /// The JSON that was sent.
180 pub payload: String,
181 /// RFC 3339.
182 pub created_at: String,
183 pub delivered_at: Option<String>,
184 pub next_attempt_at: Option<String>,
185}
186
187/// Which webhooks a call is about: a repository's, or with `repo` left out,
188/// the workspace's own.
189#[derive(Clone, Debug, Serialize, Deserialize)]
190pub struct HookOwner {
191 pub workspace: String,
192 #[serde(default)]
193 pub repo: Option<RepoPath>,
194}
195
196/// `list`. Returns `Outcome<Vec<Hook>>`. Members of the workspace only.
197#[derive(Debug, Serialize, Deserialize)]
198pub struct ListArgs {
199 pub viewer: Viewer,
200 #[serde(flatten)]
201 pub owner: HookOwner,
202}
203
204/// `create`. Returns `Outcome<CreatedHook>`. Members, for a repository's
205/// webhooks; owners, for the workspace's.
206#[derive(Debug, Serialize, Deserialize)]
207pub struct CreateArgs {
208 pub actor: User,
209 #[serde(flatten)]
210 pub owner: HookOwner,
211 pub url: String,
212 /// Event types, or `["*"]` for all. All when empty.
213 #[serde(default)]
214 pub events: Vec<String>,
215 /// Made by g1t when left out.
216 #[serde(default)]
217 pub secret: Option<String>,
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
221pub struct CreatedHook {
222 pub hook: Hook,
223 /// The secret, when g1t made it: shown this once.
224 pub secret: Option<String>,
225}
226
227/// `update`: only the fields given change. Returns `Outcome<Hook>`.
228#[derive(Debug, Serialize, Deserialize)]
229pub struct UpdateArgs {
230 pub actor: User,
231 #[serde(flatten)]
232 pub owner: HookOwner,
233 pub id: String,
234 #[serde(default)]
235 pub url: Option<String>,
236 #[serde(default)]
237 pub events: Option<Vec<String>>,
238 #[serde(default)]
239 pub active: Option<bool>,
240}
241
242/// `delete` (returns `Outcome<bool>`) and `ping` (sends a `ping` event and
243/// returns `Outcome<HookDelivery>`).
244#[derive(Debug, Serialize, Deserialize)]
245pub struct HookArgs {
246 pub actor: User,
247 #[serde(flatten)]
248 pub owner: HookOwner,
249 pub id: String,
250}
251
252/// `deliveries`: a webhook's latest deliveries, newest first. Returns
253/// `Outcome<Vec<HookDelivery>>`.
254#[derive(Debug, Serialize, Deserialize)]
255pub struct DeliveriesArgs {
256 pub viewer: Viewer,
257 #[serde(flatten)]
258 pub owner: HookOwner,
259 pub id: String,
260}
261
262/// `redeliver`: sends a delivery's payload again, as a new delivery.
263/// Returns `Outcome<HookDelivery>`.
264#[derive(Debug, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct RedeliverArgs {
267 pub actor: User,
268 #[serde(flatten)]
269 pub owner: HookOwner,
270 pub delivery_id: String,
271}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look272
273#[cfg(test)]
274mod tests {
275 use super::EVENT_TYPES;
276
277 /// The TypeScript mirror lists the same events, in the same order.
278 #[test]
279 fn the_typescript_mirror_lists_the_same_events() {
280 let ts = include_str!("../../../packages/contracts/src/webhooks.ts");
281 let list = ts
282 .split_once("export const EVENT_TYPES = [")
283 .and_then(|(_, rest)| rest.split_once("] as const"))
284 .map(|(list, _)| list)
285 .expect("EVENT_TYPES in webhooks.ts");
286 let mirrored: Vec<&str> = list
287 .split(',')
288 .map(|item| item.trim().trim_matches('"'))
289 .filter(|item| !item.is_empty())
290 .collect();
291 assert_eq!(mirrored, EVENT_TYPES);
292 }
293}

This file's history is long; its oldest lines are credited to the oldest commit read.