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.

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