| 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 | |
| 10 | import type { Release } from "./about"; |
| 11 | import type { RepoRole } from "./access"; |
| 12 | import type { CheckRunEventData, CheckSuiteEventData, StatusEventData } from "./checks"; |
| 13 | import type { DeploymentStatus, RepoDeployment } from "./deployments"; |
| 14 | import type { TeamRole, TeamVisibility } from "./teams"; |
| 15 | import 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. */ |
| 29 | export 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 | */ |
| 45 | export 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. */ |
| 55 | export 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. */ |
| 64 | export 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. */ |
| 82 | export 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. */ |
| 94 | export 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. */ |
| 103 | export 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`). */ |
| 107 | export type JoinedHow = "invitation" | "created" | "joined"; |
| 108 | |
| 109 | /** The payload of `workspace.member_joined`. Mirrors `MemberJoined` in events.rs. */ |
| 110 | export 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 | |
| 120 | export 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 | |
| 137 | export 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 | */ |
| 668 | export 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. */ |
| 678 | export 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 | |
| 688 | export type EventType = keyof EventPayloads; |
| 689 | |
| 690 | export 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`. */ |
| 707 | export 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`. */ |
| 712 | export const MAX_LIST_REPOS = 100; |
| 713 | |
| 714 | export 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 | */ |
| 737 | export 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. */ |
| 749 | export 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 | */ |
| 757 | export type ActorCount = { actor: string; count: number }; |
| 758 | |
| 759 | /** Pushes by one actor, with the commits they brought. */ |
| 760 | export 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. */ |
| 763 | export 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 | |
| 774 | export 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. */ |
| 794 | export 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 | |
| 802 | export 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. */ |
| 814 | export 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 | } |