Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

820 linesCodeBlameRaw
1/**
2 * Every state change in g1t is published as an event. Services react to
3 * each other through events rather than direct calls, and the same stream
4 * feeds timelines, webhooks and workflows.
5 *
6 * The envelope follows CloudEvents: `type` says what happened, `subject`
7 * says to what, `data` is the type-specific payload.
8 */
9
10import type { Release } from "./about";
11import type { RepoRole } from "./access";
12import type { CheckRunEventData, CheckSuiteEventData, StatusEventData } from "./checks";
13import type { DeploymentStatus, RepoDeployment } from "./deployments";
14import type { TeamRole, TeamVisibility } from "./teams";
15import type {
16 AgentRef,
17 ClaimEventData,
18 CloseReason,
19 DependencyChangedData,
20 LinkRole,
21 OverlapDetectedData,
22 Resolution,
23 SessionEventData,
24 SessionProgressedData,
25 Verdict,
26} from "./work";
27
28/** What every `work_item.*` event carries. */
29export type WorkItemEventData = {
30 itemId: string;
31 /** The workspace's slug; `number` is the work item's there. */
32 workspace: string;
33 number: number;
34 /** Every repository it is linked to, its origin first. */
35 repoIds: string[];
36 author?: { id: string; username: string };
37 requestedBy?: { id: string; username: string };
38};
39
40/**
41 * What a comment event says it is on: pull request `number` of `repoId`
42 * (with `pullId`), or work item `number` of `workspace` (with `itemId`,
43 * `repoIds` its linked repositories and `repoId` its origin, if any).
44 */
45export type CommentOnData = {
46 repoId?: string;
47 number: number;
48 pullId?: string;
49 itemId?: string;
50 workspace?: string;
51 repoIds?: string[];
52};
53
54/** What every `package.*` event names. */
55export type PackageEventData = {
56 packageId: string;
57 workspace: string;
58 ecosystem: string;
59 name: string;
60 repoId: string | null;
61};
62
63/** What `deployment.succeeded` and `deployment.failed` carry. */
64export type DeploymentEventData = {
65 deploymentId: string;
66 projectId: string;
67 repoId: string;
68 workspace: string;
69 project: string;
70 kind: "production" | "preview";
71 branch: string | null;
72 number: number | null;
73 commit: string;
74 path: string;
75 error: string | null;
76 recovered: boolean;
77 /** A username, or `g1t`. */
78 triggeredBy: string;
79};
80
81/** What every `release.*` event carries. */
82export type ReleaseEventData = {
83 releaseId: string;
84 repoId: string;
85 tagName: string;
86 release: Release;
87 /** On `release.edited`: what the title and notes were before. */
88 changes?: { name?: { from: string | null }; body?: { from: string } };
89 /** Set when a workflow job's token made the change: the run's id. */
90 causedByJob?: string;
91};
92
93/** The payload of the `repo.collaborator_*` events. */
94export type RepoCollaboratorData = {
95 repoId: string;
96 namespace: string;
97 name: string;
98 username: string;
99 role: RepoRole | null;
100 previousRole: RepoRole | null;
101};
102/** A team asked to review a pull request. */
103export type TeamRequested = { team: string; notified: string[]; assigned: string[] };
104
105/** The payload of the `team.*` events; each sets the fields that apply. */
106/** How someone came to be a member of a workspace (`workspace.member_joined`). */
107export type JoinedHow = "invitation" | "created" | "joined";
108
109/** The payload of `workspace.member_joined`. Mirrors `MemberJoined` in events.rs. */
110export type MemberJoinedData = {
111 workspaceId: string;
112 /** The workspace's slug. */
113 workspace: string;
114 userId: string;
115 username: string;
116 role: "owner" | "member";
117 how: JoinedHow;
118};
119
120export type TeamChangedData = {
121 workspace: string;
122 teamId: string;
123 team: string;
124 name: string;
125 visibility: TeamVisibility | null;
126 parent?: string;
127 changes?: string[];
128 username?: string;
129 role?: TeamRole;
130 previousRole?: TeamRole;
131 repoId?: string;
132 repo?: string;
133 repoRole?: RepoRole;
134 previousRepoRole?: RepoRole;
135};
136
137export type EventPayloads = {
138 /** `parentId`: the repository it was forked from, when it is a fork. */
139 "repo.created": { repoId: string; namespace: string; name: string; isPrivate: boolean; parentId?: string };
140 /** A repository was forked: into a workspace, or for a pull request (`pullId`). */
141 "repo.forked": { repoId: string; sourceRepoId: string; pullId?: string };
142 /** A fork left its fork network: detached, or its parent changed who can see it. */
143 "repo.detached": { repoId: string; parentId: string };
144 /**
145 * A repository's website, topics or visibility changed.
146 * `visibilityChanged` says whether it went public or private, which
147 * `repo.visibility_changed` also announces on its own.
148 */
149 "repo.updated": { repoId: string; namespace: string; name: string; isPrivate: boolean; visibilityChanged: boolean };
150 /**
151 * A project's name or description changed. `primary` says whether it is
152 * its repository's own project, whose description is the one search and
153 * repository lists show.
154 */
155 "project.updated": { projectId: string; workspace: string; slug: string; repoId: string; primary: boolean; description: string | null };
156 "repo.visibility_changed": { repoId: string; isPrivate: boolean };
157 /**
158 * A repository's name changed within its workspace `namespace`, from
159 * `from` to `to`, keeping its id. A path change like `repo.transferred`:
160 * services move rows kept under its path to its *current* path (see
161 * `repoMove`, `currentMovedPath`).
162 */
163 "repo.renamed": { repoId: string; namespace: string; from: string; to: string };
164 /**
165 * A repository was deleted. It is hidden and git refuses it, but it can
166 * be restored until `purgeAfter`: services stop what runs for it and hide
167 * it, and keep their rows until `repo.purged`.
168 */
169 "repo.deleted": {
170 repoId: string;
171 namespace: string;
172 name: string;
173 isPrivate: boolean;
174 purgeAfter: string;
175 /** It went with its workspace (`workspace.deleting`); deployments leaves it to that. */
176 withWorkspace?: boolean;
177 };
178 /** A deleted repository is back, as it was. Services start again what they stopped. */
179 "repo.restored": {
180 repoId: string;
181 namespace: string;
182 name: string;
183 isPrivate: boolean;
184 /** It came back with its workspace (`workspace.restored`). */
185 withWorkspace?: boolean;
186 };
187 /**
188 * A deleted repository is gone for good, its git data with it. Services
189 * drop every row they keep for it, except a workspace's history (ledgers,
190 * invoices, the audit log).
191 */
192 "repo.purged": { repoId: string; namespace: string; name: string };
193 /**
194 * A repository was archived (read-only: pushes, merges, agents and
195 * workflows refused; work items and pull requests locked; deployments keep
196 * serving), or unarchived.
197 */
198 "repo.archived": { repoId: string; namespace: string; name: string; archived: true };
199 "repo.unarchived": { repoId: string; namespace: string; name: string; archived: false };
200 /** The default branch is now `to`; `renamed` when `from` was renamed to it. */
201 "repo.default_branch_changed": { repoId: string; from: string; to: string; renamed: boolean };
202 /** A branch was renamed. Pull requests from it follow. */
203 "branch.renamed": { repoId: string; from: string; to: string; defaultBranch: boolean };
204 /** An account was made, or changed what its profile shows. Ask identity for the profile. */
205 "user.updated": { username: string };
206 /**
207 * An account was deleted, by the person or by g1t's staff; staff can
208 * restore it until `purgeAfter`. Its sessions, tokens and keys have ended
209 * and it has left every workspace. Services stop what they do for it and
210 * keep their rows; `user.restored` undoes that, and `user.deleted` follows
211 * once `purgeAfter` passes.
212 */
213 "user.deleting": { userId: string; username: string; byStaff: boolean; purgeAfter: string };
214 /** Staff brought a deleted account back. Services undo what they did on `user.deleting`. */
215 "user.restored": { userId: string; username: string };
216 /**
217 * An account is gone for good. Services drop what they keep for it alone
218 * and show what it wrote as `ghost` (`GHOST_USERNAME`, `GHOST_ID`).
219 * Ledgers, invoices and audit logs keep its username, which is never given
220 * to anyone again.
221 */
222 "user.deleted": { userId: string; username: string };
223 /** A workspace was made, or its name, description or icon changed. */
224 "workspace.updated": { workspaceId: string; slug: string };
225 /**
226 * A person became a member of a workspace. `how` says by which door;
227 * the workspace's creator is `created`, so a listener that greets new
228 * members leaves them out (the agents service's @g1t welcomes everyone
229 * else, once). Never for an agent or a token.
230 */
231 "workspace.member_joined": MemberJoinedData;
232 /** Someone, or g1t staff, made an invite. Never the code or the address. */
233 "invite.created": { inviteId: string; inviterId: string | null; workspaceId: string | null; bound: boolean };
234 /** An invite was used: by a new account, or by an account joining a workspace. */
235 "invite.redeemed": {
236 inviteId: string;
237 userId: string;
238 inviterId: string | null;
239 workspaceId: string | null;
240 createdAccount: boolean;
241 };
242 /**
243 * One branch moved by a push. `ref` is the full ref, `after` the commit it
244 * points to now, and `defaultBranch` whether it is the default branch.
245 */
246 /**
247 * `before` is where the ref pointed before; absent for a new branch or
248 * tag. `causedByJob` is set when a workflow job's token pushed: the run's id.
249 */
250 "git.push": {
251 repoId: string;
252 ref: string;
253 before?: string;
254 after: string;
255 defaultBranch: boolean;
256 causedByJob?: string;
257 /**
258 * How many commits the push brought to the branch, along its
259 * first-parent line from `after` back to `before` (a merge commit is
260 * one), at most 50; one for a new branch. Absent when it was not
261 * counted: a tag, a branch deleted, a pull request landed, a push the
262 * store could not read back, or one from before this was recorded.
263 */
264 commits?: number;
265 };
266 /**
267 * A branch or tag was deleted by a push. `before` is the commit it pointed
268 * to. `causedByJob` is set when a workflow job's token deleted it: the run's id.
269 */
270 "git.deleted": { repoId: string; ref: string; before: string; causedByJob?: string };
271 /**
272 * `author` is who opened it: g1t, for one its agent filed while at work,
273 * with `requestedBy` the person it was working for. Every work item and
274 * pull request event carries both. A work item's events are numbered in
275 * its workspace and carry every repository it is linked to (`repoIds`,
276 * its origin first): each is that repository's event as well.
277 */
278 "work_item.opened": WorkItemEventData & { title: string };
279 "work_item.updated": WorkItemEventData;
280 /** The people it is assigned to changed; `assignees` is the new set, `added` those newly assigned. */
281 "work_item.assigned": WorkItemEventData & { assignees: string[]; added?: string[] };
282 /**
283 * It closed: `resolution` is what landed last, when a landing closed it,
284 * and `missing` is empty.
285 */
286 "work_item.closed": WorkItemEventData & {
287 reason: CloseReason;
288 resolution?: Resolution;
289 missing?: string[];
290 /** The session it was closed in: the one its closing commit names (`G1t-Session`), or the caller's own on it. */
291 session?: string;
292 };
293 "work_item.reopened": WorkItemEventData;
294 /** What closes it landed in one repository (`resolution`); `missing` are the targets still to land in. */
295 "work_item.landed": WorkItemEventData & { resolution: Resolution; missing: string[]; session?: string };
296 /** A repository (`repo`, `owner/name`) was linked to it, or its link changed (`role`). */
297 "work_item.linked": WorkItemEventData & { repo: string; role: LinkRole };
298 "work_item.unlinked": WorkItemEventData & { repo: string; role: LinkRole };
299 /**
300 * A page of where repositories' issues went when they became work items,
301 * sent by the job that moved them; the last page has `done`.
302 */
303 "work.migrated": {
304 page: number;
305 items: { repoId: string; legacyNumber: number; workspace: string; number: number; itemId: string }[];
306 done: boolean;
307 };
308 /**
309 * `item` is the number, in the workspace, of the work item the pull request is for. `author` is
310 * who opened it: g1t, for a change g1t made, with `requestedBy` the person
311 * who asked for it.
312 */
313 "pull.opened": {
314 pullId: string;
315 repoId: string;
316 number: number;
317 item?: number;
318 agent: string;
319 author?: { id: string; username: string };
320 requestedBy?: { id: string; username: string };
321 };
322 "pull.ready": { pullId: string; repoId: string; number: number; item?: number };
323 /** A push moved the head of a pull request that is ready for review. */
324 "pull.updated": { pullId: string; repoId: string; number: number; item?: number; commit: string };
325 /** A merge was asked for while the pull request was behind; it has to catch up first. */
326 "pull.merge_requested": { pullId: string; repoId: string; number: number; item?: number };
327 "pull.closed": { pullId: string; repoId: string; number: number; item?: number };
328 /** A closed pull request was opened again. `commit` is its head. */
329 "pull.reopened": { pullId: string; repoId: string; number: number; item?: number; commit?: string };
330 /** A pull request that was ready for review was turned back into a draft. */
331 "pull.converted_to_draft": { pullId: string; repoId: string; number: number; item?: number };
332 /** People were assigned to a pull request: `assignees` is the new set, `added` those newly assigned. */
333 "pull.assigned": { pullId: string; repoId: string; number: number; item?: number; assignees: string[]; added: string[] };
334 /** Reviewers were asked for a pull request (`reviewers`), or no longer are. */
335 "pull.review_requested": {
336 pullId: string;
337 repoId: string;
338 number: number;
339 item?: number;
340 reviewers?: string[];
341 /** Teams asked: `team` is `workspace/slug`, `notified` who is told, `assigned` who review assignment picked. */
342 teams?: TeamRequested[];
343 /** Asked because they own files it changes (its CODEOWNERS file). */
344 codeOwners?: boolean;
345 };
346 "pull.review_request_removed": {
347 pullId: string;
348 repoId: string;
349 number: number;
350 item?: number;
351 reviewers?: string[];
352 teams?: TeamRequested[];
353 };
354 /** g1t stopped seeing a pull request through until a person steps in; `detail` says why. */
355 "pull.stalled": { pullId: string; repoId: string; number: number; item?: number; detail: string };
356 /** A pull request g1t had stopped on is going again. */
357 "pull.resumed": { pullId: string; repoId: string; number: number; item?: number };
358 /**
359 * The pull request's head or its target moved and the files both changed
360 * overlap: a sandbox should find out whether it still merges cleanly.
361 * `commit` is its head.
362 */
363 "pull.mergecheck": { pullId: string; repoId: string; number: number; item?: number; commit: string };
364 /** Whether the pull request merges cleanly was settled. */
365 "pull.mergeability": { pullId: string; repoId: string; number: number; item?: number };
366 "pull.merged": { pullId: string; repoId: string; number: number; item?: number; commit: string };
367 /** A run of the acceptance checks finished. `commit` is what was checked. */
368 "checks.completed": {
369 pullId: string;
370 repoId: string;
371 number: number;
372 status: "passed" | "failed" | "errored";
373 commit: string;
374 };
375 /** A status was set on a commit through the API. */
376 "status.created": StatusEventData;
377 /** A check run was reported on a commit through the API, completed, asked to run again, or had one of its buttons pressed (`requestedAction`). */
378 "check_run.created": CheckRunEventData;
379 "check_run.completed": CheckRunEventData;
380 "check_run.rerequested": CheckRunEventData;
381 "check_run.requested_action": CheckRunEventData;
382 /** A reporter's check runs on a commit all completed, or it was asked to run them again. */
383 "check_suite.completed": CheckSuiteEventData;
384 "check_suite.rerequested": CheckSuiteEventData;
385 /** A g1t agent finished reviewing a pull request; no verdict if it could not. No longer published: on older events only. */
386 "review.completed": {
387 pullId: string;
388 repoId: string;
389 number: number;
390 verdict?: "approve" | "request_changes";
391 };
392 /** A person was given a role on one repository directly, had it changed, or lost it. `role` is null once removed. */
393 "repo.collaborator_added": RepoCollaboratorData;
394 "repo.collaborator_removed": RepoCollaboratorData;
395 "repo.collaborator_role_changed": RepoCollaboratorData;
396 /** A team of a workspace was created, changed (`changes` says what) or deleted. */
397 "team.created": TeamChangedData;
398 "team.edited": TeamChangedData;
399 "team.deleted": TeamChangedData;
400 /** Someone joined a team, had their role in it changed, or left it. */
401 "team.member_added": TeamChangedData;
402 "team.member_role_changed": TeamChangedData;
403 "team.member_removed": TeamChangedData;
404 /** A team was given a role on a repository, had it changed, or lost it. */
405 "team.repo_added": TeamChangedData;
406 "team.repo_role_changed": TeamChangedData;
407 "team.repo_removed": TeamChangedData;
408 /** A repository's merge queue gained, lost or settled an entry. */
409 "queue.changed": { repoId: string };
410 /**
411 * A combined state in the merge queue failed. `pull` (also `number`)
412 * answers for it; `suspects` are the pull requests ahead it fails with,
413 * narrowed by building smaller combinations when `bisected`. With
414 * nothing ahead, though it passes its checks on its own, `landed` lists
415 * the pull requests merged into the default branch since it branched,
416 * newest first.
417 */
418 "integration.failed": {
419 repoId: string;
420 pullId: string;
421 number: number;
422 pull: number;
423 item: number | null;
424 combination: number[];
425 check: { name: string; conclusion: string; url: string | null };
426 bisected: boolean;
427 suspects: number[];
428 otherPulls: number[];
429 landed: number[];
430 commit: string | null;
431 };
432 /** A combined state in the merge queue passed. */
433 "integration.passed": {
434 repoId: string;
435 pullId: string;
436 number: number;
437 pull: number;
438 item: number | null;
439 combination: number[];
440 commit: string | null;
441 };
442 /** A decision record was kept on a work item or pull request (`number`). */
443 "decision.recorded": {
444 repoId: string;
445 number: number;
446 item: number | null;
447 decision: {
448 id: string;
449 item: number | null;
450 pull: number | null;
451 sessionId: string | null;
452 actor: string | null;
453 kind: string;
454 confidence: "claimed" | "tested" | "approved";
455 summary: string;
456 evidence: Record<string, unknown>;
457 createdAt: string;
458 };
459 };
460 /** `number` is the work item or pull request commented on. */
461 "comment.created": CommentOnData & {
462 commentId: string;
463 /** Set when the comment is a review. */
464 verdict?: Verdict;
465 /**
466 * Set when one of the workspace's agents wrote it, as itself; the
467 * event's actor is then the person it acted for (`actingFor`).
468 */
469 agent?: AgentRef;
470 actingFor?: { id: string; username: string };
471 /** An agent's review: its verdict is advisory and counts toward nothing. */
472 advisory?: boolean;
473 };
474 /** A comment's text changed; `changes.body.from` is what it said before. */
475 "comment.edited": CommentOnData & {
476 commentId: string;
477 changes: { body: { from: string } };
478 };
479 /** A comment was deleted; `comment` is the comment as it was. */
480 "comment.deleted": CommentOnData & {
481 commentId: string;
482 comment: {
483 id: string;
484 body: string;
485 author: { id: string; username: string };
486 createdAt: string;
487 path: string | null;
488 line: number | null;
489 };
490 };
491 "session.appended": { pullId: string; repoId: string; number: number; count: number };
492 /** A harness session started on the repository (not on an idempotent re-start). */
493 "session.started": SessionEventData;
494 /** A session ended, or timed out after 30 minutes without a heartbeat. */
495 "session.ended": SessionEventData;
496 /** A session (or a subagent of it) recorded more: at most every few seconds each, for the pages showing it. */
497 "session.progressed": SessionProgressedData;
498 /** Someone claimed a work item; `others` are the other active claims on it. */
499 "work.claimed": ClaimEventData;
500 "work.released": ClaimEventData;
501 "work.claim_expired": ClaimEventData;
502 "work.handoff_requested": ClaimEventData;
503 /** A work item's blockers changed, or one of them closed or had its pull request merged. */
504 "dependency.changed": DependencyChangedData;
505 /** A push made a pull request change files another open one changes. */
506 "overlap.detected": OverlapDetectedData;
507 /**
508 * A build of a project finished, for production or one pull request's
509 * preview (`number`). `path` is the deployment's page on the site;
510 * `recovered`, on a success, says the build before it failed.
511 */
512 "deployment.succeeded": DeploymentEventData;
513 "deployment.failed": DeploymentEventData;
514 /**
515 * A deployment was made, wherever it runs: reported through the API, by
516 * a g1t Actions job with an `environment:`, or a g1t.page build. Its
517 * `payload` is left out: read the deployment for it.
518 */
519 "deployment.created": { repoId: string; deployment: Omit<RepoDeployment, "payload">; causedByJob?: string };
520 /**
521 * A deployment has a new status; `deployment` is as it is now.
522 * `causedByJob` (as on every event a workflow job's token causes) is the
523 * run whose job made it, so no workflow starts for it.
524 */
525 "deployment_status.created": {
526 repoId: string;
527 deployment: Omit<RepoDeployment, "payload">;
528 deploymentStatus: DeploymentStatus;
529 causedByJob?: string;
530 };
531 /**
532 * A release changed, one event per GitHub release activity it amounts
533 * to: made (`created`; a published one is also `published`, and
534 * `released` or `prereleased`), a draft published, edited, made a draft
535 * again (`unpublished`) or deleted. `release` is as it is now (as it was,
536 * for `release.deleted`).
537 */
538 "release.created": ReleaseEventData;
539 "release.published": ReleaseEventData;
540 "release.released": ReleaseEventData;
541 "release.prereleased": ReleaseEventData;
542 "release.edited": ReleaseEventData;
543 "release.unpublished": ReleaseEventData;
544 "release.deleted": ReleaseEventData;
545 /**
546 * A package version was published, such as an image pushed by
547 * `docker push`. `tags` are the tags that now point to it; `repoId` (and
548 * the event's) is the repository the package is linked to, if any.
549 */
550 "package.published": PackageEventData & { version: string; digest: string; size: number; tags: string[] };
551 /** One version of a package was deleted, with the tags that pointed to it. */
552 "package.version_deleted": PackageEventData & { version: string; digest: string };
553 /** A package was deleted with every version it had. */
554 "package.deleted": PackageEventData;
555 /** A package became public or private. */
556 "package.visibility_changed": PackageEventData & { visibility: "public" | "private" };
557 /**
558 * A workspace's slug changed from `from` to `to`. Services that store a
559 * slug move their rows to the workspace's *current* slug (see
560 * `currentWorkspaceSlug`), so a repeated or late delivery after a second
561 * rename still lands in the right place.
562 */
563 "workspace.renamed": { workspaceId: string; from: string; to: string };
564 /**
565 * A repository moved from workspace `from` to `to`, keeping its id and
566 * name. Services that store its path or its workspace's slug move those
567 * rows to its *current* path (ask repos `path_by_id`), so a repeated or
568 * late delivery after a second transfer still lands in the right place.
569 */
570 "repo.transferred": { repoId: string; name: string; from: string; to: string };
571 /**
572 * A workspace is gone. Services drop what they keep for it alone; ledgers,
573 * invoices and the audit log stay under its slug, which is never reused.
574 */
575 "workspace.deleted": { workspaceId: string; slug: string };
576 /**
577 * An owner deleted a workspace; staff can restore it until `purgeAfter`,
578 * and nobody can reach it meanwhile. Services hide what they keep for it
579 * and stop what runs for it, keeping their rows: repos deletes its
580 * repositories softly (`repo.deleted` with `withWorkspace`), deployments
581 * pauses its apps, search drops it. `workspace.restored` undoes exactly
582 * that; `workspace.deleted` follows once `purgeAfter` passes. `by` is the
583 * owner's username.
584 */
585 "workspace.deleting": { workspaceId: string; slug: string; by: string; purgeAfter: string };
586 /**
587 * Staff brought a deleted workspace back with its members and tokens.
588 * Services undo what they did on `workspace.deleting`, and only that.
589 */
590 "workspace.restored": { workspaceId: string; slug: string };
591 /**
592 * A sandbox was stopped because it looked like it was mining: CPU pinned
593 * with little I/O and no progress, or a miner seen by name. For g1t's
594 * staff, in sudo; published with no `repoId` so it never reaches a
595 * repository's timeline or webhooks. `metrics` is what the sandbox
596 * measured (`Verdict` in crates/runner abuse.rs), or null if it could
597 * not say.
598 */
599 "abuse.flagged": {
600 workspace: string;
601 repo: string | null;
602 /** The agent run, when the sandbox had one. */
603 run: string | null;
604 /** What the sandbox was for: agent, checks, queue, actions, deploy... */
605 kind: string;
606 sandbox: string;
607 metrics: Record<string, unknown> | null;
608 };
609 /**
610 * Docs (services/artifacts): a page was made. Published with no `repoId`, so
611 * a page (which may be in a private space) never reaches a repository's
612 * timeline or webhooks; `actor` is the user's or agent's id. Readers
613 * check access with the artifacts service before showing anything of it.
614 */
615 "doc.page.created": DocPageEventData;
616 /**
617 * A page's content changed: at most once per page every ten minutes of
618 * editing (when its history records a version), and for every agent
619 * edit, accepted suggestion and restore. `authors` are member keys
620 * (`user:<id>`, `agent:<id>`) of everyone whose changes are in it.
621 */
622 "doc.page.updated": DocPageEventData & { versionId: string; kind: "edit" | "agent" | "suggestion" | "restore"; authors: string[] };
623 /** A page went to the trash (with every page under it; one event for the page asked about). */
624 "doc.page.archived": DocPageEventData;
625 /**
626 * A page became possibly out of date: a merged pull request or a push to
627 * a repository's default branch changed code it cites. `repoId` is in
628 * `data`, not on the event, for the same reason as above. `owners` are
629 * member keys; an agent that owns the page can update it
630 * (`stalePagesForAgent` in docs.ts).
631 */
632 "doc.page.stale": DocPageEventData & { repoId: string; repo: string; commit: string; pull: number | null; paths: string[]; owners: string[] };
633 /**
634 * Artifacts (folios, services/artifacts): a folio was made. Like `doc.page.*`,
635 * published with no `repoId`, never offered to webhooks, and readers check
636 * access with the artifacts service before showing anything of it.
637 */
638 "folio.created": FolioEventData;
639 /** A folio's content changed: a version (`versionKind`) with everyone whose changes are in it. */
640 "folio.updated": FolioEventData & { versionId: string; versionKind: "edit" | "agent" | "suggestion" | "proposal" | "restore"; authors: string[] };
641 /** A folio went to the trash (with everything under it; one event for the folio asked about). */
642 "folio.trashed": FolioEventData;
643 "folio.restored": FolioEventData;
644 /**
645 * A folio was deleted for good from the trash (with everything under it;
646 * one event for each top that went): by someone with full access, by
647 * emptying the trash, or by the daily purge (`actor` null then).
648 */
649 "folio.deleted": FolioEventData;
650 /** Someone was given access: who (member keys) and the role. Never content. */
651 "folio.shared": FolioEventData & { principals: string[]; role: "view" | "comment" | "edit" | "manage" };
652 /** Code a folio cites changed. The repository is in `data` as `owner/name` only. */
653 "folio.stale": FolioEventData & { repo: string; commit: string; pull: number | null; paths: string[]; owners: string[] };
654 /**
655 * A space was deleted (services/artifacts): everything in it went to the
656 * trash (`trashed` counts the artifacts; this one event stands for them
657 * all). Like `folio.*`, published with no `repoId` and never offered to
658 * webhooks; `name` is null for a members-only space.
659 */
660 "doc.space.deleted": { workspace: string; workspaceId: string; spaceId: string; name: string | null; kind: "workspace" | "team" | "private"; trashed: number };
661};
662
663/**
664 * What every `folio.*` event carries. `title` is null unless every member
665 * of the workspace can read the folio, so a private folio's name never
666 * travels.
667 */
668export type FolioEventData = {
669 workspace: string;
670 workspaceId: string;
671 folioId: string;
672 kind: "doc" | "slides" | "design" | "dashboard";
673 spaceId: string | null;
674 title: string | null;
675};
676
677/** What every `doc.page.*` event carries. */
678export type DocPageEventData = {
679 workspace: string;
680 workspaceId: string;
681 pageId: string;
682 spaceId: string;
683 title: string;
684 /** The page's address on the site. */
685 path: string;
686};
687
688export type EventType = keyof EventPayloads;
689
690export type G1tEvent<T extends EventType = EventType> = {
691 [K in T]: {
692 id: string;
693 type: K;
694 /** The service that published it. */
695 source: string;
696 /** RFC 3339. */
697 time: string;
698 /** The repo the event concerns, used to scope timelines and deliveries. */
699 repoId: string | null;
700 /** The user or agent that caused it, if any. */
701 actor: string | null;
702 data: EventPayloads[K];
703 };
704}[T];
705
706/** What a publisher supplies; the bus fills in `id` and `time`. */
707export type NewEvent<T extends EventType = EventType> = {
708 [K in T]: Omit<G1tEvent<K>, "id" | "time">;
709}[T];
710
711/** The most repositories one `list` reads with `repoIds`. */
712export const MAX_LIST_REPOS = 100;
713
714export type EventQuery = {
715 repoId?: string;
716 /** Only events of these repositories, by id (at most `MAX_LIST_REPOS`); the caller has checked the viewer may read each. */
717 repoIds?: string[];
718 types?: EventType[];
719 /** Return events older than this event id. */
720 before?: string;
721 /** Only events by this account id. */
722 actor?: string;
723 /** Only events about these work items or pull requests (`number`, or the `item` a comment or review is on). */
724 numbers?: number[];
725 /** Only events at or after this RFC 3339 time. */
726 since?: string;
727 limit?: number;
728};
729
730/**
731 * `activity_digest`: what happened in some repositories, and to a
732 * workspace's artifacts, over `[from, until)`, counted by who did it. Home
733 * reads it for what people and agents did since you were last there.
734 * snake_case, as the Rust service reads it
735 * (`g1t_contracts::events::ActivityDigestArgs`).
736 */
737export type ActivityDigestArgs = {
738 /** By id; the caller has checked the viewer may read each. At most `MAX_DIGEST_REPOS` are read. */
739 repo_ids: string[];
740 /** The workspace's slug, for its `folio.*` events (which carry no repository); null leaves artifacts out. */
741 workspace: string | null;
742 /** RFC 3339, inclusive. */
743 from: string;
744 /** RFC 3339, exclusive. */
745 until: string;
746};
747
748/** The most repositories one digest reads. */
749export const MAX_DIGEST_REPOS = 50;
750
751/**
752 * How many times one actor did something. The actor is a member key:
753 * `user:<id>` for a person (or g1t, `user:usr_g1t_agent`), `agent:<id>` for
754 * one of the workspace's agents acting as itself, or `""` when the event
755 * named nobody.
756 */
757export type ActorCount = { actor: string; count: number };
758
759/** Pushes by one actor, with the commits they brought. */
760export type PushesBy = { actor: string; count: number; commits: number };
761
762/** Pushes to a repository's branches (tags are not pushes here). A push whose commits were not counted counts as one commit. */
763export type PushDigest = {
764 count: number;
765 commits: number;
766 /** The branches pushed to, by name, sorted. */
767 branches: string[];
768 /** Most pushes first. */
769 by: PushesBy[];
770 /** Of those, the pushes straight to the default branch: what landed without a pull request. */
771 default_branch: { count: number; commits: number; by: PushesBy[]; last_at: string | null };
772};
773
774export type RepoDigest = {
775 repo_id: string;
776 pushes: PushDigest;
777 /** Pull requests opened, merged and closed without merging, each by who did it. */
778 pulls: { opened: ActorCount[]; merged: ActorCount[]; closed: ActorCount[] };
779 /** Work items linked to it opened and closed, each by who did it. */
780 items: { opened: ActorCount[]; closed: ActorCount[] };
781 /** `review.completed` (g1t's), and a comment with a verdict (a person's). */
782 reviews: ActorCount[];
783 /** Comments without a verdict; one by an agent as itself is the agent's. */
784 comments: ActorCount[];
785 /** Builds that finished, by who set them off; `production` is how many successes were production. */
786 deployments: { succeeded: ActorCount[]; failed: ActorCount[]; production: number };
787 /** Releases published, newest first, at most 50. */
788 releases: { tag: string; name: string | null; actor: string; at: string }[];
789 /** Package versions published, newest first, at most 50. */
790 packages: { ecosystem: string; name: string; version: string; actor: string; at: string }[];
791};
792
793/** A workspace's artifacts over the span. Never a title. */
794export type FolioDigest = {
795 created: ActorCount[];
796 /** Each folio whose content changed, once, with everyone whose changes are in it (member keys); at most 50. */
797 edited: { folio_id: string; kind: "doc" | "slides" | "design" | "dashboard" | string; authors: string[] }[];
798 /** How many folios changed, counting those past the cap. */
799 edited_count: number;
800};
801
802export type ActivityDigest = {
803 from: string;
804 until: string;
805 /** One per repository that had any event counted; none for a quiet one. */
806 repos: RepoDigest[];
807 /** When a workspace was named. */
808 folios: FolioDigest | null;
809 /** False when more events fell in the span than were read, or more repositories were named than are read, so counts are low. */
810 complete: boolean;
811};
812
813/** The event bus and its durable log. */
814export interface EventsApi {
815 publish(events: NewEvent[]): Promise<void>;
816 /** Newest first. */
817 list(query: EventQuery): Promise<G1tEvent[]>;
818 /** A span of some repositories' and a workspace's artifacts' events, counted by who did it. */
819 activityDigest(args: ActivityDigestArgs): Promise<ActivityDigest>;
820}