flagon-io/g1t

public

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

g1t/crates/contracts/src/events.rs

598 lines20,532 bytesCodeBlame

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.

Events service in Rust, with RFC 3339 times and accurate push events1//! Events published on the bus, and the events service that carries them.
2//! Mirrors `packages/contracts/src/events.ts`.
Rust repos service with shipping; pull requests kept in the model3
4use serde::Serialize;
5
6/// What a publisher supplies; the bus fills in the id and time.
7#[derive(Debug, Serialize)]
8#[serde(rename_all = "camelCase")]
9pub struct NewEvent<T: Serialize> {
10 #[serde(rename = "type")]
11 pub kind: &'static str,
12 /// The service that published it.
13 pub source: &'static str,
14 /// The repo the event concerns.
15 pub repo_id: Option<String>,
16 /// The user or agent that caused it, if any.
17 pub actor: Option<String>,
18 pub data: T,
19}
20
21#[derive(Debug, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct RepoCreated {
24 pub repo_id: String,
25 pub namespace: String,
26 pub name: String,
27 pub is_private: bool,
28}
29
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look30/// The payload of `repo.collaborator_added`, `repo.collaborator_removed`
31/// and `repo.collaborator_role_changed`: a person's own role on one
32/// repository (see `access`). `role` is the role they have now (null once
33/// removed); `previous_role` what they had before (null when added).
Rust repos service with shipping; pull requests kept in the model34#[derive(Debug, Serialize)]
35#[serde(rename_all = "camelCase")]
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look36pub struct RepoCollaborator {
37 pub repo_id: String,
38 pub namespace: String,
39 pub name: String,
40 pub username: String,
41 pub role: Option<crate::access::RepoRole>,
42 pub previous_role: Option<crate::access::RepoRole>,
43}
44
45#[derive(Debug, Serialize)]
46#[serde(rename_all = "camelCase")]
Rust repos service with shipping; pull requests kept in the model47pub struct RepoForked {
48 pub repo_id: String,
49 pub source_repo_id: String,
Issues and pull requests replace intents and attempts50 pub pull_id: String,
Rust repos service with shipping; pull requests kept in the model51}
52
GitHub Actions on g1t, part one: reading workflows53/// One branch or tag moved by a push. `after` is the commit it points to now.
Rust repos service with shipping; pull requests kept in the model54#[derive(Debug, Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct GitPush {
57 pub repo_id: String,
Events service in Rust, with RFC 3339 times and accurate push events58 /// The full ref, such as `refs/heads/main`.
Rust repos service with shipping; pull requests kept in the model59 #[serde(rename = "ref")]
60 pub git_ref: String,
GitHub Actions on g1t, part one: reading workflows61 /// Where it pointed before; absent for a new branch or tag.
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub before: Option<String>,
Rust repos service with shipping; pull requests kept in the model64 pub after: String,
Events service in Rust, with RFC 3339 times and accurate push events65 /// Whether the ref is the repository's default branch.
66 pub default_branch: bool,
Rust repos service with shipping; pull requests kept in the model67}
Work service in Rust, with RFC 3339 timestamps68
Agents as a team: lifecycle, merge queue, billing and a new shell69/// The payload of `issue.opened`, `issue.updated`, `issue.assigned`,
70/// `issue.closed` and `issue.reopened`; each uses the fields that apply to it.
Issues and pull requests replace intents and attempts71#[derive(Debug, Default, Serialize)]
Work service in Rust, with RFC 3339 timestamps72#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts73pub struct IssueEvent {
74 pub issue_id: String,
Work service in Rust, with RFC 3339 timestamps75 pub repo_id: String,
76 pub number: u32,
Issues and pull requests replace intents and attempts77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub title: Option<String>,
79 /// On close: `completed` or `not_planned`.
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub reason: Option<&'static str>,
82 /// On close: the number of the pull request whose merge closed it.
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub resolved_by: Option<u32>,
Agents as a team: lifecycle, merge queue, billing and a new shell85 /// On `issue.assigned`: the people it is now assigned to.
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub assignees: Option<Vec<String>>,
Work service in Rust, with RFC 3339 timestamps88}
89
Acceptance checks in sandboxes, line comments and review verdicts90/// The payload of `pull.opened`, `pull.ready`, `pull.updated` (its head
91/// moved), `pull.closed` and `pull.merged`; each uses the fields that apply to it.
Work service in Rust, with RFC 3339 timestamps92#[derive(Debug, Default, Serialize)]
93#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts94pub struct PullEvent {
95 pub pull_id: String,
Work service in Rust, with RFC 3339 timestamps96 pub repo_id: String,
Issues and pull requests replace intents and attempts97 pub number: u32,
98 /// The number of the issue it is for.
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub issue: Option<u32>,
Work service in Rust, with RFC 3339 timestamps101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub agent: Option<String>,
Workflows run when an agent's pull request is marked ready103 /// On merge: the commit the branch now points to. On update and when
104 /// marked ready: the head of the change.
Work service in Rust, with RFC 3339 timestamps105 #[serde(skip_serializing_if = "Option::is_none")]
Issues and pull requests replace intents and attempts106 pub commit: Option<String>,
107 /// On close: the pull request that was merged instead.
Work service in Rust, with RFC 3339 timestamps108 #[serde(skip_serializing_if = "Option::is_none")]
Issues and pull requests replace intents and attempts109 pub superseded_by: Option<u32>,
Work service in Rust, with RFC 3339 timestamps110}
111
Acceptance checks in sandboxes, line comments and review verdicts112/// `checks.completed`: a run of an issue's acceptance checks against a pull
113/// request finished.
114#[derive(Debug, Serialize)]
115#[serde(rename_all = "camelCase")]
116pub struct ChecksEvent {
117 pub pull_id: String,
118 pub repo_id: String,
119 pub number: u32,
120 /// `passed`, `failed` or `errored`.
121 pub status: &'static str,
122 /// The commit that was checked.
123 pub commit: String,
124}
125
Actions: workflow_run, workflow.completed, artifacts on the run page, Node 24126/// `workflow.completed`: a GitHub Actions run finished.
127#[derive(Debug, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct WorkflowEvent {
130 pub run_id: String,
131 pub repo_id: String,
132 /// The workflow's name, and its file.
133 pub workflow: String,
134 pub path: String,
135 /// The run's number among the workflow's runs.
136 pub number: u64,
137 /// The GitHub event that started it, such as `push`.
138 pub event: String,
139 /// `success`, `failure`, `cancelled` or `skipped`.
140 pub conclusion: String,
141 #[serde(rename = "ref")]
142 pub git_ref: String,
143 pub sha: String,
144 /// The pull request it ran for, if any.
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub pull: Option<u32>,
147}
148
Agents as a team: lifecycle, merge queue, billing and a new shell149/// `review.completed`: a g1t agent finished reviewing a pull request, or
150/// could not.
151#[derive(Debug, Serialize)]
152#[serde(rename_all = "camelCase")]
153pub struct ReviewEvent {
154 pub pull_id: String,
155 pub repo_id: String,
156 pub number: u32,
157 /// `approve` or `request_changes`; absent when no review was written.
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub verdict: Option<&'static str>,
160}
161
Issues and pull requests replace intents and attempts162/// `comment.created`. `number` is the issue or pull request commented on.
Work service in Rust, with RFC 3339 timestamps163#[derive(Debug, Serialize)]
164#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts165pub struct CommentCreated {
166 pub comment_id: String,
167 pub repo_id: String,
168 pub number: u32,
Agents as a team: lifecycle, merge queue, billing and a new shell169 /// Set when the comment is on a pull request.
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub pull_id: Option<String>,
172 /// Set when the comment is a review: approve or request changes.
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub verdict: Option<crate::work::Verdict>,
Issues and pull requests replace intents and attempts175}
176
177#[derive(Debug, Serialize)]
178#[serde(rename_all = "camelCase")]
Work service in Rust, with RFC 3339 timestamps179pub struct SessionAppended {
Issues and pull requests replace intents and attempts180 pub pull_id: String,
181 pub repo_id: String,
182 pub number: u32,
Work service in Rust, with RFC 3339 timestamps183 pub count: u32,
184}
185
Events service in Rust, with RFC 3339 times and accurate push events186/// An event as stored in the log and delivered to subscribers. `data` is
187/// left as JSON; each reader decodes the types it cares about.
188#[derive(Clone, Debug, Serialize, serde::Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Event {
191 /// Sorts by the time the event was published.
192 pub id: String,
193 #[serde(rename = "type")]
194 pub kind: String,
195 /// The service that published it.
196 pub source: String,
197 /// RFC 3339.
198 pub time: String,
199 /// The repo the event concerns.
200 pub repo_id: Option<String>,
201 /// The user or agent that caused it, if any.
202 pub actor: Option<String>,
203 pub data: serde_json::Value,
204}
205
206/// `publish`, as a publisher sends it. Returns nothing.
207#[derive(Debug, Serialize)]
208pub struct Publish<T: Serialize> {
209 pub events: Vec<NewEvent<T>>,
210}
211
212/// `publish`, as the events service reads it.
213#[derive(Debug, serde::Deserialize)]
214pub struct PublishArgs {
215 pub events: Vec<Published>,
216}
217
218/// A [`NewEvent`] of any type, as received.
Work service in Rust, with RFC 3339 timestamps219#[derive(Debug, serde::Deserialize)]
220#[serde(rename_all = "camelCase")]
Events service in Rust, with RFC 3339 times and accurate push events221pub struct Published {
Work service in Rust, with RFC 3339 timestamps222 #[serde(rename = "type")]
223 pub kind: String,
Events service in Rust, with RFC 3339 times and accurate push events224 pub source: String,
225 #[serde(default)]
Work service in Rust, with RFC 3339 timestamps226 pub repo_id: Option<String>,
Events service in Rust, with RFC 3339 times and accurate push events227 #[serde(default)]
228 pub actor: Option<String>,
Work service in Rust, with RFC 3339 timestamps229 pub data: serde_json::Value,
230}
Events service in Rust, with RFC 3339 times and accurate push events231
232/// `list`: events from the log, newest first. Returns `Vec<Event>`.
233#[derive(Debug, Default, Serialize, serde::Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct ListArgs {
236 #[serde(default)]
237 pub repo_id: Option<String>,
238 /// Only these types; all types when empty.
239 #[serde(default)]
240 pub types: Vec<String>,
241 /// Only events older than this event id.
242 #[serde(default)]
243 pub before: Option<String>,
244 #[serde(default)]
245 pub limit: Option<u32>,
246}
Agents as a team: lifecycle, merge queue, billing and a new shell247
Agents and memory, checks and conflicts, profiles, slug renames, custom domains248/// `workspace.renamed`: a workspace's slug changed from `from` to `to`.
249/// Every service that stores a slug moves its rows to the workspace's
250/// *current* slug (ask identity by `workspace_id`), so that a repeated or
251/// late delivery after a second rename still lands in the right place.
252#[derive(Clone, Debug, Serialize, serde::Deserialize)]
253#[serde(rename_all = "camelCase")]
254pub struct WorkspaceRenamed {
255 pub workspace_id: String,
256 pub from: String,
257 pub to: String,
258}
259
260impl WorkspaceRenamed {
261 /// The slugs whose rows move to `current`: the two this rename names,
262 /// minus `current` itself. Moving rows keyed by either converges on the
263 /// current slug whatever order renames are delivered in.
264 pub fn stale_slugs(&self, current: &str) -> Vec<String> {
265 let mut slugs: Vec<String> = Vec::new();
266 for slug in [&self.from, &self.to] {
267 if slug != current && !slugs.contains(slug) {
268 slugs.push(slug.clone());
269 }
270 }
271 slugs
272 }
273}
274
Search across all of g1t, Explore, and a command palette275/// `repo.updated`: a repository's description, topics or visibility
276/// changed. `visibility_changed` says whether it went public or private,
277/// which `repo.visibility_changed` also announces on its own.
278#[derive(Debug, Serialize, serde::Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct RepoUpdated {
281 pub repo_id: String,
282 pub namespace: String,
283 pub name: String,
284 pub is_private: bool,
285 #[serde(default)]
286 pub visibility_changed: bool,
287}
288
289/// `repo.visibility_changed`: a repository went public or private.
290#[derive(Debug, Serialize, serde::Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct RepoVisibilityChanged {
293 pub repo_id: String,
294 pub is_private: bool,
295}
296
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look297/// `repo.renamed`: a repository's name changed within its workspace,
298/// keeping its id and its git store key. Like `repo.transferred`, a path
299/// change: every service that keeps rows under the repository's path moves
300/// them to its *current* path (ask repos `path_by_id`), so a repeated or
301/// late delivery after a second rename or a transfer still lands in the
302/// right place. `g1t_kit::transfer::on_event` handles both.
303#[derive(Clone, Debug, Serialize, serde::Deserialize)]
Search across all of g1t, Explore, and a command palette304#[serde(rename_all = "camelCase")]
305pub struct RepoRenamed {
306 pub repo_id: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look307 /// The workspace it is in.
Search across all of g1t, Explore, and a command palette308 pub namespace: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look309 /// Its old name.
310 pub from: String,
311 /// Its new name.
312 pub to: String,
313}
314
315impl RepoRenamed {
316 /// The paths whose rows move to `current` (`namespace/name`): the two
317 /// this rename names, minus `current`.
318 pub fn stale_paths(&self, current: &str) -> Vec<String> {
319 let mut paths: Vec<String> = Vec::new();
320 for name in [&self.from, &self.to] {
321 let path = format!("{}/{name}", self.namespace);
322 if path != current && !paths.contains(&path) {
323 paths.push(path);
324 }
325 }
326 paths
327 }
328}
329
330/// `repo.deleted`: a repository was deleted. It is hidden everywhere and
331/// git refuses it, but it can be restored until `purge_after`, so services
332/// stop what runs for it (agents, workflows, deployments, indexing,
333/// webhook deliveries) and hide it, and keep what they hold until
334/// `repo.purged`. `repo.restored` brings it back.
335#[derive(Clone, Debug, Serialize, serde::Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct RepoDeleted {
338 pub repo_id: String,
339 #[serde(default)]
340 pub namespace: String,
341 #[serde(default)]
Search across all of g1t, Explore, and a command palette342 pub name: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look343 #[serde(default)]
344 pub is_private: bool,
345 /// RFC 3339: when it is purged unless restored first.
346 #[serde(default)]
347 pub purge_after: String,
Search across all of g1t, Explore, and a command palette348}
349
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look350/// `repo.restored`: a deleted repository is back, at its path, as it was.
351/// Services start again what `repo.deleted` stopped: index it, deploy its
352/// production, show it.
353#[derive(Clone, Debug, Serialize, serde::Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub struct RepoRestored {
356 pub repo_id: String,
357 pub namespace: String,
358 pub name: String,
359 pub is_private: bool,
360}
361
362/// `repo.purged`: a deleted repository is gone for good, its git data
363/// with it. Services drop every row they keep for it by `repo_id`, except
364/// history that belongs to its workspace: ledgers, invoices and the audit
365/// log. Its path is free for a new repository.
366#[derive(Clone, Debug, Serialize, serde::Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct RepoPurged {
369 pub repo_id: String,
370 pub namespace: String,
371 pub name: String,
372}
373
374/// `repo.archived` and `repo.unarchived`: a repository became read-only,
375/// or writable again. While archived, pushes are refused, issues and pull
376/// requests are locked, and agents and workflows do not run for it; its
377/// deployments keep serving.
378#[derive(Clone, Debug, Serialize, serde::Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct RepoArchived {
381 pub repo_id: String,
382 pub namespace: String,
383 pub name: String,
384 pub archived: bool,
385}
386
387/// `repo.default_branch_changed`: the branch everything lands on is now
388/// `to`. `renamed` says whether `from` was renamed to `to` (open pull
389/// requests into it now target `to`) rather than another branch chosen.
390#[derive(Clone, Debug, Serialize, serde::Deserialize)]
391#[serde(rename_all = "camelCase")]
392pub struct RepoDefaultBranchChanged {
393 pub repo_id: String,
394 pub from: String,
395 pub to: String,
396 #[serde(default)]
397 pub renamed: bool,
398}
399
400/// `branch.renamed`: a branch was renamed. Pull requests from or into
401/// `from` follow it to `to`, and web addresses that name `from` redirect.
402#[derive(Clone, Debug, Serialize, serde::Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct BranchRenamed {
405 pub repo_id: String,
406 pub from: String,
407 pub to: String,
408 /// Whether it is the default branch.
409 #[serde(default)]
410 pub default_branch: bool,
411}
412
413/// `repo.transferred`: a repository moved from one workspace to another,
414/// keeping its id and its name. Every service that keeps a repository
415/// under its path (`namespace/name`) or its workspace's slug moves those
416/// rows to the repository's *current* path (ask repos `path_by_id`), so a
417/// repeated or late delivery after a second transfer still lands in the
418/// right place. What was charged or recorded before the transfer stays
419/// with the workspace it happened in.
420#[derive(Clone, Debug, Serialize, serde::Deserialize)]
Search across all of g1t, Explore, and a command palette421#[serde(rename_all = "camelCase")]
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look422pub struct RepoTransferred {
Search across all of g1t, Explore, and a command palette423 pub repo_id: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look424 pub name: String,
425 /// The workspace it left.
426 pub from: String,
427 /// The workspace it went to.
428 pub to: String,
Search across all of g1t, Explore, and a command palette429}
430
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look431impl RepoTransferred {
432 /// The paths whose rows move to `current` (`namespace/name`): the two
433 /// this transfer names, minus `current`. Moving rows keyed by either
434 /// converges whatever order transfers are delivered in.
435 pub fn stale_paths(&self, current: &str) -> Vec<String> {
436 let mut paths: Vec<String> = Vec::new();
437 for namespace in [&self.from, &self.to] {
438 let path = format!("{namespace}/{}", self.name);
439 if path != current && !paths.contains(&path) {
440 paths.push(path);
441 }
442 }
443 paths
444 }
445}
446
447/// `workspace.deleted`: a workspace is gone. Services drop what they keep
448/// for it alone (its webhooks, integrations, secrets, memory, guardrails,
449/// agent queue) and keep what is history: ledgers, invoices and the audit
450/// log stay under its slug, which is never given to another workspace.
451#[derive(Clone, Debug, Serialize, serde::Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct WorkspaceDeleted {
454 pub workspace_id: String,
455 pub slug: String,
456}
457
Search across all of g1t, Explore, and a command palette458/// `user.updated`: an account was made, or changed what its profile shows
459/// (name, bio, avatar). Nothing private: ask identity for the profile.
460#[derive(Debug, Serialize, serde::Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct UserUpdated {
463 pub username: String,
464}
465
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look466/// `user.email_added`, `user.email_verified`, `user.email_removed` and
467/// `user.primary_email_changed`: an account's addresses changed. Never the
468/// address itself; ask identity, as the person, for that.
469#[derive(Debug, Serialize, serde::Deserialize)]
470#[serde(rename_all = "camelCase")]
471pub struct UserEmailChanged {
472 pub user_id: String,
473 /// Whether g1t staff made the change.
474 #[serde(default)]
475 pub by_staff: bool,
476}
477
Search across all of g1t, Explore, and a command palette478/// `workspace.updated`: a workspace was made, or its name, description or
479/// icon changed. Ask identity for it by slug.
480#[derive(Debug, Serialize, serde::Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct WorkspaceUpdated {
483 pub workspace_id: String,
484 pub slug: String,
485}
486
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look487/// `invite.created`: someone (or staff) made an invite. Never the code or
488/// the address it is for.
489#[derive(Debug, Serialize, serde::Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct InviteCreated {
492 pub invite_id: String,
493 /// The account that made it; null when staff did.
494 pub inviter_id: Option<String>,
495 /// The workspace it joins.
496 pub workspace_id: Option<String>,
497 /// Whether it is bound to one email address.
498 pub bound: bool,
499}
500
501/// `invite.redeemed`: an invite was used, by a new account or by an
502/// existing one joining a workspace.
503#[derive(Debug, Serialize, serde::Deserialize)]
504#[serde(rename_all = "camelCase")]
505pub struct InviteRedeemed {
506 pub invite_id: String,
507 pub user_id: String,
508 pub inviter_id: Option<String>,
509 pub workspace_id: Option<String>,
510 /// Whether it made the account.
511 pub created_account: bool,
512}
513
514/// `waitlist.requested`: someone asked for access. Ask identity's staff
515/// methods for the entry; the address is not in the event.
516#[derive(Debug, Serialize, serde::Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct WaitlistRequested {
519 pub entry_id: String,
520}
521
Agents as a team: lifecycle, merge queue, billing and a new shell522/// `queue.changed`: a repository's merge queue gained, lost or settled an
523/// entry, so the next batch may be ready to test.
524#[derive(Debug, Serialize)]
525#[serde(rename_all = "camelCase")]
526pub struct QueueChanged {
527 pub repo_id: String,
528}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 fn renamed(from: &str, to: &str) -> WorkspaceRenamed {
535 WorkspaceRenamed {
536 workspace_id: "wsp_1".into(),
537 from: from.into(),
538 to: to.into(),
539 }
540 }
541
542 #[test]
543 fn stale_slugs_leave_out_the_current_one() {
544 assert_eq!(renamed("a", "b").stale_slugs("b"), vec!["a"]);
545 // Delivered after a second rename, b → c: both move to c.
546 assert_eq!(renamed("a", "b").stale_slugs("c"), vec!["a", "b"]);
547 // Renamed back: a → b → a.
548 assert_eq!(renamed("a", "b").stale_slugs("a"), vec!["b"]);
549 }
550
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look551 fn transferred(from: &str, to: &str) -> RepoTransferred {
552 RepoTransferred {
553 repo_id: "rep_1".into(),
554 name: "rocket".into(),
555 from: from.into(),
556 to: to.into(),
557 }
558 }
559
560 #[test]
561 fn stale_paths_leave_out_the_current_one() {
562 assert_eq!(transferred("a", "b").stale_paths("b/rocket"), vec!["a/rocket"]);
563 // Delivered after a second transfer, b → c: both move to c.
564 assert_eq!(
565 transferred("a", "b").stale_paths("c/rocket"),
566 vec!["a/rocket", "b/rocket"]
567 );
568 // Transferred back: a → b → a.
569 assert_eq!(transferred("a", "b").stale_paths("a/rocket"), vec!["b/rocket"]);
570 }
571
572 #[test]
573 fn a_transfer_reads_as_published() {
574 let data = serde_json::json!({ "repoId": "rep_1", "name": "rocket", "from": "a", "to": "b" });
575 let event: RepoTransferred = serde_json::from_value(data).unwrap();
576 assert_eq!((event.from.as_str(), event.to.as_str()), ("a", "b"));
577 }
578
579 #[test]
580 fn a_rename_names_both_paths_in_its_workspace() {
581 let renamed = RepoRenamed {
582 repo_id: "rep_1".into(),
583 namespace: "acme".into(),
584 from: "old".into(),
585 to: "new".into(),
586 };
587 assert_eq!(renamed.stale_paths("acme/new"), vec!["acme/old"]);
588 // Delivered after a transfer: both names in acme move.
589 assert_eq!(renamed.stale_paths("flagon/new"), vec!["acme/old", "acme/new"]);
590 }
591
Agents and memory, checks and conflicts, profiles, slug renames, custom domains592 #[test]
593 fn reads_the_published_payload() {
594 let data = serde_json::json!({ "workspaceId": "wsp_1", "from": "a", "to": "b" });
595 let event: WorkspaceRenamed = serde_json::from_value(data).unwrap();
596 assert_eq!((event.from.as_str(), event.to.as_str()), ("a", "b"));
597 }
598}