Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 1 | //! Which events each subscribing service's queue is sent. |
| 2 | //! | |
| 3 | //! The events service passes each batch on to one queue per subscriber | |
| 4 | //! (`SUBSCRIBER_*` bindings). Each is sent only the types its consumer acts | |
| 5 | //! on, so an event costs a queue write for the services that read it, not | |
| 6 | //! for all of them. A consumer that starts acting on a new type adds it | |
| 7 | //! here in the same change, or it never hears of it; the tests below and | |
| 8 | //! beside each consumer check what they can. | |
| 9 | //! | |
| 10 | //! A pattern is an exact type (`git.push`), a family (`pull.*`, every type | |
| 11 | //! that starts `pull.`), or `*`, every type. A binding not named here is | |
| 12 | //! sent everything, so a new subscriber is never left out. | |
| 13 | ||
| 14 | /// What the shared helpers in `g1t_kit` act on (`rename`, `transfer`, | |
| 15 | /// `lifecycle`, `deleted`, `user_deleted`): rows that follow a workspace | |
| 16 | /// or repository, or go with it. Rare, so every subscriber is sent them. | |
| 17 | const LIFECYCLE: [&str; 8] = [ | |
| 18 | "workspace.renamed", | |
| 19 | "workspace.deleted", | |
| 20 | "repo.renamed", | |
| 21 | "repo.transferred", | |
| 22 | "repo.deleted", | |
| 23 | "repo.restored", | |
| 24 | "repo.purged", | |
| 25 | "user.deleted", | |
| 26 | ]; | |
| 27 | ||
| 28 | /// Each subscriber's binding and the types it is sent beside [`LIFECYCLE`]. | |
| 29 | pub const ROUTES: &[(&str, &[&str])] = &[ | |
| 30 | // Memory captured from merges and comments, mentions, pull request | |
| 31 | // bases, runs stopped in archived repositories (work/src/lib.rs queue). | |
| 32 | ( | |
| 33 | "SUBSCRIBER_WORK", | |
| 34 | &[ | |
| 35 | "git.push", | |
| 36 | "branch.renamed", | |
| 37 | "repo.created", | |
| 38 | "repo.archived", | |
| 39 | "repo.default_branch_changed", | |
| 40 | "pull.merged", | |
| 41 | "comment.created", | |
| 42 | ], | |
| 43 | ), | |
| 44 | // Pull requests g1t is seeing through, mentions, queued issues and the | |
| 45 | // merge queue (runner/src/index.ts queue). | |
| 46 | ( | |
| 47 | "SUBSCRIBER_RUNNER", | |
| 48 | &[ | |
| 49 | "pull.opened", | |
| 50 | "pull.ready", | |
| 51 | "pull.updated", | |
| 52 | "pull.mergeability", | |
| 53 | "pull.mergecheck", | |
| 54 | "pull.merge_requested", | |
| 55 | "pull.merged", | |
| 56 | "pull.closed", | |
| 57 | "checks.completed", | |
| 58 | "queue.changed", | |
| 59 | "comment.created", | |
| 60 | "issue.opened", | |
| 61 | "issue.updated", | |
| 62 | "issue.closed", | |
| 63 | "repo.archived", | |
| 64 | ], | |
| 65 | ), | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 66 | // Write-back to linked trackers (integrations/src lib.rs). |
| 67 | ("SUBSCRIBER_INTEGRATIONS", &["issue.closed", "pull.opened"]), | |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 68 | // A hook may ask for any type, or for every one. |
| 69 | ("SUBSCRIBER_WEBHOOKS", &["*"]), | |
| 70 | // Every event a workflow can run on (g1t_actions::events::github_events), | |
| 71 | // and runs stopped in archived repositories. | |
| 72 | ( | |
| 73 | "SUBSCRIBER_ACTIONS", | |
| 74 | &[ | |
| 75 | "git.push", | |
| Merge branch 'worktree-agent-a8b78c62188153aa3' into claude/cool-newton-7nm8r9 | 76 | "git.deleted", |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 77 | "pull.*", |
| 78 | "issue.*", | |
| 79 | "comment.*", | |
| 80 | "release.*", | |
| 81 | "deployment.created", | |
| 82 | "deployment_status.created", | |
| 83 | "workflow.completed", | |
| 84 | "repo.archived", | |
| 85 | ], | |
| 86 | ), | |
| 87 | // Previews and production deploys (deployments/src/index.ts queue). | |
| 88 | ( | |
| 89 | "SUBSCRIBER_DEPLOYMENTS", | |
| 90 | &[ | |
| 91 | "git.push", | |
| 92 | "branch.renamed", | |
| 93 | "repo.default_branch_changed", | |
| 94 | "pull.opened", | |
| 95 | "pull.ready", | |
| 96 | "pull.updated", | |
| 97 | "pull.reopened", | |
| 98 | "pull.closed", | |
| 99 | "pull.merged", | |
| 100 | "workspace.deleting", | |
| 101 | "workspace.restored", | |
| 102 | ], | |
| 103 | ), | |
| 104 | // Projects follow their repositories, and wake on activity | |
| Merge branch 'worktree-agent-a5b0d9e2733e9e382' | 105 | // (projects/src/index.ts, activity.ts). A production deployment |
| 106 | // reported from elsewhere asks for its screenshot. | |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 107 | ( |
| 108 | "SUBSCRIBER_PROJECTS", | |
| 109 | &[ | |
| 110 | "git.push", | |
| 111 | "repo.*", | |
| 112 | "issue.opened", | |
| 113 | "issue.closed", | |
| 114 | "issue.reopened", | |
| 115 | "pull.opened", | |
| 116 | "pull.updated", | |
| 117 | "pull.merged", | |
| 118 | "pull.closed", | |
| 119 | "comment.created", | |
| 120 | "deployment.succeeded", | |
| 121 | "deployment.failed", | |
| Merge branch 'worktree-agent-a5b0d9e2733e9e382' | 122 | "deployment_status.created", |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 123 | "package.published", |
| 124 | "package.deleted", | |
| 125 | "package.version_deleted", | |
| 126 | "workspace.deleting", | |
| 127 | "workspace.restored", | |
| 128 | ], | |
| 129 | ), | |
| 130 | // Pull request working copies, workspaces' repositories, contributors | |
| 131 | // shown as ghost (repos/src/lib.rs handle_event). | |
| 132 | ( | |
| 133 | "SUBSCRIBER_REPOS", | |
| 134 | &[ | |
| 135 | "pull.merged", | |
| 136 | "pull.closed", | |
| 137 | "pull.reopened", | |
| 138 | "workspace.deleting", | |
| 139 | "workspace.restored", | |
| 140 | "user.deleting", | |
| 141 | "user.restored", | |
| 142 | ], | |
| 143 | ), | |
| 144 | // Usage rows that follow a renamed workspace or repository. | |
| 145 | ("SUBSCRIBER_BILLING", &[]), | |
| 146 | // Secret and dependency scans, and version update pull requests | |
| 147 | // (security/src/lib.rs on_event). | |
| 148 | ( | |
| 149 | "SUBSCRIBER_SECURITY", | |
| 150 | &[ | |
| 151 | "git.push", | |
| 152 | "pull.opened", | |
| 153 | "pull.updated", | |
| 154 | "pull.ready", | |
| 155 | "pull.merged", | |
| 156 | "pull.closed", | |
| 157 | "comment.created", | |
| 158 | "repo.created", | |
| 159 | "repo.visibility_changed", | |
| 160 | ], | |
| 161 | ), | |
| 162 | // The site-wide search index (search/src/index.rs). | |
| 163 | ("SUBSCRIBER_SEARCH", &["git.push", "repo.*", "issue.*", "pull.*", "user.*", "workspace.*"]), | |
| 164 | // Composer packages read from repositories, and packages that follow | |
| 165 | // their workspace, team or repository (packages/src/lib.rs on_event). | |
| 166 | ( | |
| 167 | "SUBSCRIBER_PACKAGES", | |
| 168 | &[ | |
| 169 | "git.push", | |
| 170 | "repo.visibility_changed", | |
| 171 | "workspace.deleting", | |
| 172 | "workspace.restored", | |
| 173 | "team.edited", | |
| 174 | "team.deleted", | |
| 175 | ], | |
| 176 | ), | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 177 | // Pages that cite code a merge or a push to the default branch |
| 178 | // changed become possibly out of date, and projects' docs folders shown | |
| 179 | // in Docs are read again (docs/src/staleness.ts, repo-spaces.ts). | |
| 180 | ("SUBSCRIBER_DOCS", &["git.push", "pull.merged"]), | |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 181 | ]; |
| 182 | ||
| 183 | /// Whether `pattern` (see the module's notes) matches the type `kind`. | |
| 184 | fn matches(pattern: &str, kind: &str) -> bool { | |
| 185 | match pattern.strip_suffix('*') { | |
| 186 | Some(prefix) => kind.starts_with(prefix), | |
| 187 | None => pattern == kind, | |
| 188 | } | |
| 189 | } | |
| 190 | ||
| 191 | /// Whether the subscriber behind `binding` is sent events of type `kind`. | |
| 192 | pub fn routed(binding: &str, kind: &str) -> bool { | |
| 193 | match ROUTES.iter().find(|(name, _)| *name == binding) { | |
| 194 | Some((_, patterns)) => { | |
| 195 | LIFECYCLE.contains(&kind) || patterns.iter().any(|pattern| matches(pattern, kind)) | |
| 196 | } | |
| 197 | None => true, | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | #[cfg(test)] | |
| 202 | mod tests { | |
| 203 | use super::*; | |
| 204 | ||
| 205 | /// Types published on the bus that hooks are not offered. | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 206 | const UNOFFERED: [&str; 29] = [ |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 207 | "pull.mergecheck", |
| 208 | "pull.mergeability", | |
| 209 | "deployment.review_requested", | |
| 210 | "memory.changed", | |
| 211 | "abuse.flagged", | |
| 212 | "invite.created", | |
| 213 | "invite.redeemed", | |
| 214 | "user.updated", | |
| 215 | "user.deleting", | |
| 216 | "user.restored", | |
| 217 | "user.deleted", | |
| 218 | "user.email_added", | |
| 219 | "workspace.updated", | |
| 220 | "workspace.renamed", | |
| 221 | "workspace.deleting", | |
| 222 | "workspace.restored", | |
| 223 | "workspace.deleted", | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 224 | // Docs pages: no repository, so no repository's hooks. |
| 225 | "doc.page.created", | |
| 226 | "doc.page.updated", | |
| 227 | "doc.page.archived", | |
| 228 | "doc.page.stale", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 229 | "doc.space.deleted", |
| The artifacts plan records what Phase 1 decided, self-hosting and the deploy list name the g1t-folios index and the trash cron, and folio events are listed as published but never offered to webhooks. | 230 | // Artifacts (folios): no repository either, and a folio may be private. |
| 231 | "folio.created", | |
| 232 | "folio.updated", | |
| 233 | "folio.trashed", | |
| 234 | "folio.restored", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 235 | "folio.deleted", |
| The artifacts plan records what Phase 1 decided, self-hosting and the deploy list name the g1t-folios index and the trash cron, and folio events are listed as published but never offered to webhooks. | 236 | "folio.shared", |
| 237 | "folio.stale", | |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 238 | ]; |
| 239 | ||
| 240 | fn published(kind: &str) -> bool { | |
| 241 | crate::webhooks::EVENT_TYPES.contains(&kind) || UNOFFERED.contains(&kind) | |
| 242 | } | |
| 243 | ||
| 244 | #[test] | |
| 245 | fn every_exact_route_names_a_type_that_is_published() { | |
| 246 | for (binding, patterns) in ROUTES { | |
| 247 | for pattern in patterns.iter().chain(LIFECYCLE.iter()) { | |
| 248 | if pattern.ends_with('*') { | |
| 249 | let prefix = pattern.trim_end_matches('*'); | |
| 250 | assert!( | |
| 251 | prefix.is_empty() | |
| 252 | || crate::webhooks::EVENT_TYPES.iter().chain(UNOFFERED.iter()).any(|kind| kind.starts_with(prefix)), | |
| 253 | "{binding}: {pattern} matches nothing" | |
| 254 | ); | |
| 255 | } else { | |
| 256 | assert!(published(pattern), "{binding}: {pattern} is not a type anything publishes"); | |
| 257 | } | |
| 258 | } | |
| 259 | } | |
| 260 | } | |
| 261 | ||
| 262 | #[test] | |
| 263 | fn every_route_is_a_subscriber_binding_named_once() { | |
| 264 | let mut names: Vec<&str> = ROUTES.iter().map(|(name, _)| *name).collect(); | |
| 265 | assert!(names.iter().all(|name| name.starts_with("SUBSCRIBER_"))); | |
| 266 | let count = names.len(); | |
| 267 | names.sort_unstable(); | |
| 268 | names.dedup(); | |
| 269 | assert_eq!(names.len(), count); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 270 | assert_eq!(count, 13); |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 271 | } |
| 272 | ||
| 273 | #[test] | |
| 274 | fn subscribers_hear_what_they_act_on_and_not_the_rest() { | |
| 275 | assert!(routed("SUBSCRIBER_WEBHOOKS", "secret_scanning_alert.created")); | |
| 276 | assert!(routed("SUBSCRIBER_WEBHOOKS", "anything.new")); | |
| 277 | assert!(routed("SUBSCRIBER_ACTIONS", "pull.labeled")); | |
| 278 | assert!(routed("SUBSCRIBER_ACTIONS", "release.published")); | |
| 279 | assert!(!routed("SUBSCRIBER_ACTIONS", "check_run.completed")); | |
| 280 | assert!(routed("SUBSCRIBER_RUNNER", "comment.created")); | |
| 281 | assert!(!routed("SUBSCRIBER_RUNNER", "status.created")); | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 282 | assert!(routed("SUBSCRIBER_INTEGRATIONS", "issue.closed")); |
| 283 | assert!(!routed("SUBSCRIBER_INTEGRATIONS", "git.push")); | |
| Merge branch 'worktree-agent-a049c374e6f1c6be4' into claude/cool-newton-7nm8r9 | 284 | assert!(!routed("SUBSCRIBER_INTEGRATIONS", "check_run.completed")); |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 285 | assert!(routed("SUBSCRIBER_BILLING", "workspace.renamed")); |
| 286 | assert!(!routed("SUBSCRIBER_BILLING", "git.push")); | |
| 287 | assert!(routed("SUBSCRIBER_SEARCH", "user.updated")); | |
| 288 | assert!(routed("SUBSCRIBER_REPOS", "user.deleting")); | |
| 289 | assert!(!routed("SUBSCRIBER_REPOS", "git.push")); | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 290 | assert!(routed("SUBSCRIBER_DOCS", "pull.merged")); |
| 291 | assert!(!routed("SUBSCRIBER_DOCS", "pull.opened")); | |
| Merge branch 'worktree-agent-a5b0d9e2733e9e382' | 292 | assert!(routed("SUBSCRIBER_PROJECTS", "deployment_status.created")); |
| 293 | assert!(!routed("SUBSCRIBER_PROJECTS", "deployment.created")); | |
| Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails | 294 | // Every subscriber follows what moves or removes a repository. |
| 295 | for (binding, _) in ROUTES { | |
| 296 | for kind in LIFECYCLE { | |
| 297 | assert!(routed(binding, kind), "{binding} misses {kind}"); | |
| 298 | } | |
| 299 | } | |
| 300 | } | |
| 301 | ||
| 302 | #[test] | |
| 303 | fn a_subscriber_not_in_the_table_hears_everything() { | |
| 304 | assert!(routed("SUBSCRIBER_NEW", "git.push")); | |
| 305 | assert!(routed("SUBSCRIBER_NEW", "status.created")); | |
| 306 | } | |
| 307 | ||
| 308 | #[test] | |
| 309 | fn a_family_matches_only_its_own_types() { | |
| 310 | assert!(matches("pull.*", "pull.opened")); | |
| 311 | assert!(!matches("pull.*", "pulls.opened")); | |
| 312 | assert!(!matches("pull.*", "pull")); | |
| 313 | assert!(matches("*", "x")); | |
| 314 | assert!(matches("git.push", "git.push")); | |
| 315 | assert!(!matches("git.push", "git.pushed")); | |
| 316 | } | |
| 317 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.