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.

g1t.sh
1,620 linesCodeBlameRaw

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.

Fixes from the page sweep1//! The identity service: accounts, credentials and sessions.
2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
5
6use serde::{Deserialize, Serialize};
7
8use crate::User;
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct SshKey {
13 pub id: String,
14 pub title: String,
15 pub fingerprint: String,
16 /// RFC 3339.
17 pub created_at: String,
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca18 /// When it was last used to sign in over SSH, RFC 3339, to within 5
19 /// minutes; null when it never was.
20 #[serde(default)]
21 pub last_used_at: Option<String>,
Fixes from the page sweep22}
23
Fine-grained personal tokens, workspace token rules and approvals in identity24#[derive(Clone, Debug, Default, Serialize, Deserialize)]
Fixes from the page sweep25#[serde(rename_all = "camelCase")]
26pub struct AccessToken {
27 pub id: String,
28 pub name: String,
29 /// RFC 3339.
30 pub created_at: String,
31 /// RFC 3339, to within a few minutes. Null until it is first used.
32 pub last_used_at: Option<String>,
33 /// For a workspace's token, the username of the member who made it.
34 /// Null once that account is gone, and on personal tokens.
35 pub created_by: Option<String>,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers36 /// Its scopes, as `resource:level`, the highest of each resource.
37 /// Null: full access (an application's or an agent's credential).
Fixes from the page sweep38 #[serde(default)]
39 pub scopes: Option<Vec<String>>,
40 /// Made before tokens had scopes: full access until someone narrows it.
41 #[serde(default)]
42 pub legacy: bool,
43 /// RFC 3339. Null: it does not expire.
44 #[serde(default)]
45 pub expires_at: Option<String>,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers46 /// Its scopes as permissions: each resource it may use, by name, at
47 /// the highest level, such as `{"issues": "write"}`. Every resource at
48 /// its highest when `scopes` is null.
Fine-grained personal tokens, workspace token rules and approvals in identity49 #[serde(default)]
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers50 pub permissions: std::collections::BTreeMap<String, String>,
Fine-grained personal tokens, workspace token rules and approvals in identity51 /// What it is for, as its owner wrote it.
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub description: Option<String>,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers54 /// A personal token's reach: the workspace it is made for, by slug;
55 /// null for every workspace its owner belongs to (or, with
56 /// `repository_selection` public, none). Null on a workspace's own
57 /// token, which reaches its workspace.
58 #[serde(default)]
59 pub workspace: Option<String>,
60 /// Which repositories of that workspace it reaches.
61 #[serde(default)]
62 pub repository_selection: crate::scopes::RepositorySelection,
63 /// With `selected`: the repositories, as `owner/name`, that the viewer
64 /// can see.
65 #[serde(default)]
66 pub repositories: Vec<String>,
67 /// Whether a token made for a workspace that approves tokens may be
68 /// used there yet.
69 #[serde(default)]
70 pub status: crate::tokens::TokenStatus,
71 /// Why an owner denied or revoked it.
Fine-grained personal tokens, workspace token rules and approvals in identity72 #[serde(default, skip_serializing_if = "Option::is_none")]
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers73 pub review_reason: Option<String>,
74 /// Whether it is a workspace's own token, acting as the workspace.
75 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
76 pub workspace_owned: bool,
77 /// A workspace's own token with Repositories: admin, which acts as an
78 /// admin of the workspace's repositories rather than with Write.
Fine-grained personal tokens, workspace token rules and approvals in identity79 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
80 pub admin: bool,
Merge main into Artifacts Phase 281 /// A person's token its owner let use the website (g1t.sh) as them,
82 /// with `Authorization: Bearer`. See [`crate::scopes::TokenAccess::website`].
83 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
84 pub website: bool,
Fixes from the page sweep85}
86
87/// `sign_in`: verifies a username, or any confirmed email address of the
88/// account, and its password, for website sign-in. Wrong passwords are
89/// counted against the account and `client`, and past a limit nothing is
90/// checked for a while (see identity's `throttle.rs`).
91/// Returns `Outcome<SignedIn>`.
92#[derive(Debug, Serialize, Deserialize)]
93pub struct SignInArgs {
94 pub username: String,
95 pub password: String,
96 /// Who is asking, such as the visitor's IP address, for rate limits.
97 #[serde(default)]
98 pub client: Option<String>,
99}
100
101#[derive(Debug, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct SignedIn {
104 pub user: User,
Merge main (membership, two-factor, GitHub repo roles) into tokens105 /// Empty while `two_factor_challenge` is set: no session is made until
106 /// the code is given.
Fixes from the page sweep107 pub session_token: String,
Merge main (membership, two-factor, GitHub repo roles) into tokens108 /// Set when the account has two-factor authentication on: the token to
109 /// pass to `two_factor_sign_in` with a code. Valid for
110 /// `accounts::TWO_FACTOR_CHALLENGE_SECONDS`.
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub two_factor_challenge: Option<String>,
Fixes from the page sweep113}
114
115/// `sign_out` and `user_for_session`.
116#[derive(Debug, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct SessionArgs {
119 pub session_token: String,
120}
121
122/// `user_for_git_credentials`: the account password or an access token.
123#[derive(Debug, Serialize, Deserialize)]
124pub struct GitCredentialsArgs {
125 pub username: String,
126 pub secret: String,
127}
128
129/// `user_for_access_token`.
130#[derive(Debug, Serialize, Deserialize)]
131pub struct TokenArgs {
132 pub token: String,
133}
134
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca135/// `user_for_ssh_key`, and `principal_for_ssh_key` (see [`crate::deploy_keys`]).
Fixes from the page sweep136#[derive(Debug, Serialize, Deserialize)]
137pub struct FingerprintArgs {
138 pub fingerprint: String,
139}
140
141/// `user_by_username`.
142#[derive(Debug, Serialize, Deserialize)]
143pub struct UsernameArgs {
144 pub username: String,
145}
146
147/// `usernames`: the names behind account and workspace ids, as events and
148/// other records store them. Returns a map from id to name; ids it does
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97149/// not know are left out. Also `accounts`: the accounts behind user ids,
150/// each with its username and avatar (`HashMap<String, accounts::EmailOwner>`).
Fixes from the page sweep151#[derive(Debug, Serialize, Deserialize)]
152pub struct UsernamesArgs {
153 pub ids: Vec<String>,
154}
155
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar156/// `display_usernames`: how each of these people (by lowercased username,
157/// at most 200) wrote their username, for showing it beside the key.
158/// Returns a map from the lowercased username to its chosen case; people
159/// who chose none, and names nobody has, are left out.
160#[derive(Debug, Default, Serialize, Deserialize)]
161pub struct DisplayUsernamesArgs {
162 pub usernames: Vec<String>,
163}
164
Fixes from the page sweep165/// `list_ssh_keys` and `list_access_tokens`.
166#[derive(Debug, Serialize, Deserialize)]
167pub struct UserArgs {
168 pub user: User,
169}
170
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge171/// `ssh_key_owners`: services only. The account (user id) that registered
172/// each key, by fingerprint (`SHA256:…`, as `ssh-keygen -lf` prints it),
173/// for verifying commits signed with SSH keys. Returns a map of the
174/// fingerprints found to user ids.
175#[derive(Debug, Serialize, Deserialize)]
176pub struct SshKeyOwnersArgs {
177 pub fingerprints: Vec<String>,
178}
179
Fixes from the page sweep180/// `add_ssh_key`: `public_key` is one line in OpenSSH format.
181/// Returns `Outcome<SshKey>`.
182#[derive(Debug, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct AddSshKeyArgs {
185 pub user: User,
186 pub title: String,
187 pub public_key: String,
188}
189
190/// `remove_ssh_key` and `remove_access_token`.
191#[derive(Debug, Serialize, Deserialize)]
192pub struct RemoveArgs {
193 pub user: User,
194 pub id: String,
195}
196
197/// `create_access_token`: a token that acts as `user`. For a workspace
198/// acting through a token of its own, the new token belongs to that
199/// workspace too.
200#[derive(Debug, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct CreateAccessTokenArgs {
203 pub user: User,
204 pub name: String,
205 /// When set, the token stops working after this many seconds and is
206 /// left out of the user's token list, unless `listed`. Used for hosted
207 /// attempts.
208 #[serde(default)]
209 pub ttl_seconds: Option<u64>,
210 /// Its scopes, as `resource:level`; unknown names are left out. Null:
211 /// full access.
212 #[serde(default)]
213 pub scopes: Option<Vec<String>>,
214 /// Listed with the person's tokens although it expires: one they made
215 /// themselves, with an expiry.
216 #[serde(default)]
217 pub listed: bool,
218}
219
Merge branch 'worktree-agent-a3abfcce648e87dca'220/// `create_job_token`: a workflow job's `G1T_TOKEN`. It acts as the
221/// repository's workspace, reaches that repository only, holds `scopes`
222/// (from the job's `permissions`), and is never listed. The actions service
223/// revokes it when the job ends (`revoke_job_tokens`); `ttl_seconds` is a
224/// backstop. Returns `CreatedAccessToken`.
225#[derive(Debug, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct CreateJobTokenArgs {
228 /// The workspace the repository belongs to, as its own principal.
229 pub workspace: User,
230 pub repo: crate::repos::RepoPath,
231 pub run_id: String,
232 pub job_id: String,
233 /// What the token is listed as in logs: `G1T_TOKEN for acme/web run 4`.
234 pub name: String,
235 pub ttl_seconds: u64,
236 /// As `resource:level`; unknown names are left out.
237 pub scopes: Vec<String>,
238 /// Whether it may open and approve pull requests (`JobToken::pull_requests`).
239 #[serde(default)]
240 pub pull_requests: bool,
241}
242
243/// `revoke_job_tokens`: ends a workflow job's tokens at once, when the job
244/// finishes or is cancelled. Only job tokens are touched. Returns `bool`.
245#[derive(Debug, Default, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct RevokeJobTokensArgs {
248 pub job_id: String,
249}
250
Fixes from the page sweep251/// The plaintext token is returned once and never stored.
252#[derive(Debug, Serialize, Deserialize)]
253pub struct CreatedAccessToken {
254 pub token: String,
255 pub info: AccessToken,
256}
257
258/// `register`: creates an account and signs it in.
259/// Returns `Outcome<SignedIn>`.
260///
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.261/// Anyone may register. `invite_code` is optional: an invitation sent to
262/// an address without an account (`invite_member`, or a repository
263/// invitation) carries one, and using it applies the invitation once the
264/// address is confirmed.
Fixes from the page sweep265#[derive(Debug, Serialize, Deserialize)]
266pub struct RegisterArgs {
267 pub username: String,
268 pub email: String,
269 pub password: String,
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.270 /// An invite code such as `g1t-k7m2-q9xd-4hpw-…`, from an invitation.
271 /// One that is unknown, spent or for another address is ignored.
Fixes from the page sweep272 #[serde(default)]
273 pub invite_code: Option<String>,
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm274 /// The `proof` from the invite email's link. When it is the invite's
275 /// own and `email` is the address the invite was sent to, the account
276 /// starts with that address confirmed; otherwise it is ignored.
277 #[serde(default)]
278 pub email_proof: Option<String>,
Fixes from the page sweep279 /// Who is asking, such as the visitor's IP address, for rate limits.
280 #[serde(default)]
281 pub client: Option<String>,
282}
283
284/// `verify_email`: the token from the emailed link. Returns `Outcome<User>`.
285#[derive(Debug, Serialize, Deserialize)]
286pub struct EmailTokenArgs {
287 pub token: String,
288}
289
290/// `request_password_reset`. Always succeeds, so it cannot be used to find
291/// out which addresses have accounts. Any confirmed address of an account
292/// works: the link goes to the address given, and the primary (and the
293/// backup) are told a reset was asked for. A few requests an hour per
294/// address and per `client`; past that, nothing is sent.
295#[derive(Debug, Serialize, Deserialize)]
296pub struct EmailArgs {
297 pub email: String,
298 /// Who is asking, such as the visitor's IP address, for rate limits.
299 #[serde(default)]
300 pub client: Option<String>,
301}
302
303/// `reset_password`: sets a new password and ends every session.
304/// Returns `Outcome<User>`.
305#[derive(Debug, Serialize, Deserialize)]
306pub struct ResetPasswordArgs {
307 pub token: String,
308 pub password: String,
309}
310
311/// `device_start`: begins a device sign-in. Returns `DeviceStart`.
312#[derive(Debug, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct DeviceStartArgs {
315 /// What is asking, shown to the person approving, e.g. "Claude Code".
316 pub client_name: String,
317}
318
319#[derive(Debug, Serialize, Deserialize)]
320#[serde(rename_all = "camelCase")]
321pub struct DeviceStart {
322 /// Secret held by the tool and exchanged for a token once approved.
323 pub device_code: String,
324 /// Short code shown to the person, e.g. `WDJB-MJHT`.
325 pub user_code: String,
326 /// Seconds until both codes stop working.
327 pub expires_in: u32,
328 /// Seconds the tool should wait between polls.
329 pub interval: u32,
330}
331
332/// `device_lookup`: what a user code is asking for, or null if it is not
333/// valid. Returns `Option<DeviceRequest>`.
334#[derive(Debug, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct DeviceLookupArgs {
337 pub user_code: String,
338}
339
340#[derive(Debug, Serialize, Deserialize)]
341#[serde(rename_all = "camelCase")]
342pub struct DeviceRequest {
343 pub user_code: String,
344 pub client_name: String,
345}
346
347/// `device_resolve`: the signed-in person approves or denies a request.
348/// Returns `Outcome<bool>`.
349#[derive(Debug, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct DeviceResolveArgs {
352 pub user_code: String,
353 pub user: User,
354 pub approve: bool,
355}
356
357/// `device_claim`: the tool asks whether its request was approved.
358#[derive(Debug, Serialize, Deserialize)]
359#[serde(rename_all = "camelCase")]
360pub struct DeviceClaimArgs {
361 pub device_code: String,
362}
363
364/// The answer to a `device_claim`.
365#[derive(Debug, Serialize, Deserialize)]
366#[serde(tag = "status", rename_all = "snake_case")]
367pub enum DeviceClaim {
368 /// Nobody has approved or denied it yet; ask again after the interval.
369 Pending,
370 Denied,
371 /// The code was never issued, has expired, or was already used.
372 Expired,
373 /// The access token, returned once.
374 Approved {
375 token: String,
376 user: User,
377 },
378}
379
380/// A workspace: the owner of repositories, and the first segment of their
381/// URLs. A person's own space and a team's are the same thing.
382#[derive(Clone, Debug, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct Workspace {
385 pub id: String,
386 pub slug: String,
387 pub name: String,
388 /// One line saying what the workspace is for.
389 pub description: Option<String>,
390 /// RFC 3339.
391 pub created_at: String,
392 pub member_count: u32,
393 /// The workspace's uploaded icon: the SHA-256 of its bytes, served at
394 /// `/avatars/<avatar>`. Null means the generated letter avatar.
395 #[serde(default)]
396 pub avatar: Option<String>,
397 /// What every member gets on each of its repositories; owners have
398 /// Admin. See [`crate::access`].
399 #[serde(default)]
400 pub base_permission: crate::access::BasePermission,
Merge branch 'worktree-agent-ad7c6d88d93adc817'401 /// Who may create its teams. See [`crate::teams::TeamCreation`].
402 #[serde(default)]
403 pub team_creation: crate::teams::TeamCreation,
Merge main (membership, two-factor, GitHub repo roles) into tokens404 /// What members may do, by GitHub's names for each
405 /// (`members_can_create_public_repositories`...), at the top level as
406 /// GitHub's organization has them. See [`crate::MemberPrivileges`].
407 #[serde(flatten)]
408 pub privileges: crate::MemberPrivileges,
409 /// Whether members and outside collaborators need two-factor
410 /// authentication to use it.
411 #[serde(default)]
412 pub two_factor_requirement_enabled: bool,
Fixes from the page sweep413}
414
415#[derive(Clone, Debug, Serialize, Deserialize)]
416pub struct Member {
417 pub username: String,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar418 /// The username as its owner wrote it (`Ana`), when that differs from
419 /// `username`: what pages show.
420 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub display_username: Option<String>,
Fixes from the page sweep422 pub role: crate::Role,
Merge main (membership, two-factor, GitHub repo roles) into tokens423 /// The roles they hold besides `role`.
424 #[serde(default)]
425 pub org_roles: Vec<crate::OrgRole>,
426 /// Whether they have two-factor authentication on. Shown to owners
427 /// only; null for anyone else.
428 #[serde(default)]
429 pub two_factor: Option<bool>,
Fixes from the page sweep430 /// Their display name, when they set one.
431 #[serde(default)]
432 pub name: Option<String>,
433 /// Their uploaded avatar: the SHA-256 of its bytes, served at
434 /// `/avatars/<avatar>`. None means the generated letter avatar.
435 #[serde(default)]
436 pub avatar: Option<String>,
437}
438
Merge branch 'worktree-agent-a2013627e5ea4ab13'439/// Where a workspace keeps its repositories' git data: anywhere g1t
440/// stores it (the default), or in the EU only. It applies to repositories
441/// made after it is set; the repos service reads it when it places a new
442/// one (`storage_options` says whether the EU can be chosen).
443#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
444#[serde(rename_all = "lowercase")]
445pub enum DataResidency {
446 #[default]
447 Anywhere,
448 Eu,
449}
450
451impl DataResidency {
452 pub fn as_str(self) -> &'static str {
453 match self {
454 DataResidency::Anywhere => "anywhere",
455 DataResidency::Eu => "eu",
456 }
457 }
458
459 pub fn parse(text: &str) -> Option<Self> {
460 match text.trim().to_ascii_lowercase().as_str() {
461 "anywhere" => Some(DataResidency::Anywhere),
462 "eu" => Some(DataResidency::Eu),
463 _ => None,
464 }
465 }
466}
467
468/// `workspace_residency` takes [`SlugArgs`] and returns
469/// `Option<DataResidency>` (null when there is no such workspace).
470/// `set_workspace_residency`: owners only. Returns `Outcome<DataResidency>`.
471#[derive(Debug, Serialize, Deserialize)]
472pub struct SetResidencyArgs {
473 pub actor: User,
474 pub slug: String,
475 pub residency: DataResidency,
476}
477
Fixes from the page sweep478/// `create_workspace`. Returns `Outcome<Workspace>`.
479#[derive(Debug, Serialize, Deserialize)]
480pub struct CreateWorkspaceArgs {
481 pub user: User,
482 pub slug: String,
483 #[serde(default)]
484 pub name: String,
485}
486
487/// `get_workspace`: public details, or null. Returns `Option<Workspace>`.
488#[derive(Debug, Serialize, Deserialize)]
489pub struct SlugArgs {
490 pub slug: String,
491}
492
Merge main (membership, two-factor, GitHub repo roles) into tokens493/// `list_members`: members only. Owners also see each member's
494/// `two_factor`. Returns `Outcome<Vec<Member>>`.
Fixes from the page sweep495#[derive(Debug, Serialize, Deserialize)]
496pub struct ListMembersArgs {
497 pub slug: String,
498 pub viewer: crate::Viewer,
499}
500
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)501/// `add_member` and `remove_member`: owners only. `add_member` never adds a
502/// person at once: it sends them a workspace invitation to accept or
503/// decline, as `invite_member` with a username does. Only g1t's own agent
504/// is added at once. Removing yourself is
Merge main (membership, two-factor, GitHub repo roles) into tokens505/// leaving (`members::LeaveWorkspaceArgs`); removing an owner is refused
506/// when they are the last. Each returns `Outcome<bool>`.
Fixes from the page sweep507#[derive(Debug, Serialize, Deserialize)]
508pub struct MemberArgs {
509 pub actor: User,
510 pub slug: String,
511 pub username: String,
Merge main (membership, two-factor, GitHub repo roles) into tokens512 #[serde(default)]
513 pub surface: Option<crate::audit::Surface>,
Fixes from the page sweep514}
515
516/// `update_workspace`: owners only. An empty name falls back to the slug;
517/// an empty description clears it. Returns `Outcome<Workspace>`.
518#[derive(Debug, Serialize, Deserialize)]
519pub struct UpdateWorkspaceArgs {
520 pub actor: User,
521 pub slug: String,
522 pub name: String,
523 pub description: String,
524}
525
526/// `rename_workspace`: owners only. Changes the workspace's slug, the first
527/// segment of its URLs, to `new_slug`; the display name is untouched. The
528/// old slug redirects to the new one, and is held for this workspace, for
529/// [`SLUG_HOLD_DAYS`]. Publishes `workspace.renamed`. Returns
530/// `Outcome<Workspace>`.
531///
532/// `check_workspace_rename` takes the same arguments and answers whether
533/// the rename would be allowed, changing nothing. Returns `Outcome<bool>`.
534#[derive(Debug, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub struct RenameWorkspaceArgs {
537 pub actor: User,
538 pub slug: String,
539 pub new_slug: String,
540}
541
542/// `delete_workspace`: owners only, and only a person. `confirm` must be
543/// the workspace's slug, typed out. Refused for a protected workspace
544/// ([`protected_names`]), whoever asks, and while billing cannot settle it
545/// (`close_workspace`). Everything in it goes with it at once: nobody can
546/// reach it, its tokens stop working, its pages are not found, and its
547/// repositories, projects and apps are deleted with it. It is kept for
548/// [`WORKSPACE_RESTORE_DAYS`] so g1t's staff can restore it, then purged:
549/// its memberships, access tokens and old-slug redirects go, and billing's
550/// ledger and the audit log keep its history. The slug is never given to
551/// another workspace; the person whose username it is may make a workspace
552/// of that name again once it is purged. Publishes `workspace.deleting`,
553/// and `workspace.deleted` at the purge. Returns `Outcome<bool>`.
554///
555/// `check_workspace_deletion` takes the same arguments (with `confirm`
556/// ignored) and says what would go and whether anything stands in the way,
557/// changing nothing. Returns `Outcome<WorkspaceDeletion>`.
558#[derive(Debug, Serialize, Deserialize)]
559pub struct DeleteWorkspaceArgs {
560 pub actor: User,
561 pub slug: String,
562 #[serde(default)]
563 pub confirm: String,
564 /// Where the request came in, for the audit log; g1t.sh when absent.
565 #[serde(default)]
566 pub surface: Option<crate::audit::Surface>,
567}
568
569/// What deleting a workspace takes with it, and what stands in the way.
570/// Nothing does when `billing` is null and it is not `protected`.
571#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
572pub struct WorkspaceDeletion {
573 /// Its live repositories, which are deleted with it.
574 pub repositories: u32,
575 /// Its projects, hidden with it.
576 pub projects: u32,
577 #[serde(default)]
578 pub members: u32,
579 /// Why billing cannot close the workspace yet, in words for its owner.
580 pub billing: Option<String>,
581 /// It can never be deleted, by anyone ([`protected_names`]).
582 #[serde(default)]
583 pub protected: bool,
584}
585
586impl WorkspaceDeletion {
587 pub fn blocked(&self) -> bool {
588 self.protected || self.billing.is_some()
589 }
590
591 /// Why the workspace cannot be deleted, as one sentence, or `None`.
592 pub fn reason(&self, slug: &str) -> Option<String> {
593 if self.protected {
594 return Some(protected_refusal(slug));
595 }
596 self.billing.clone()
597 }
598}
599
600/// How long a deleted workspace is kept, for staff to restore, before it is
601/// purged.
602pub const WORKSPACE_RESTORE_DAYS: u64 = 30;
603
604/// Workspaces nobody can delete, whatever identity's `PROTECTED_WORKSPACES`
605/// says: Flagon's, which runs g1t.
606pub const ALWAYS_PROTECTED: &[&str] = &["flagon-io"];
607
608/// The protected workspaces: `configured` (comma-separated slugs or
609/// workspace ids, as identity's `PROTECTED_WORKSPACES` holds them), and
610/// [`ALWAYS_PROTECTED`] whatever it says, so an empty or missing variable
611/// still protects them. Lowercased, without duplicates.
612pub fn protected_names(configured: Option<&str>) -> Vec<String> {
613 let mut names: Vec<String> = Vec::new();
614 let given = configured.unwrap_or_default().split(',');
615 for name in ALWAYS_PROTECTED.iter().copied().chain(given) {
616 let name = name.trim().to_lowercase();
617 if !name.is_empty() && !names.contains(&name) {
618 names.push(name);
619 }
620 }
621 names
622}
623
624/// Why a protected workspace is not deleted, purged or acted on.
625pub fn protected_refusal(slug: &str) -> String {
626 format!("{slug} is protected and can never be deleted.")
627}
628
629/// `admin_deleted_workspaces` takes no arguments (`{}`) and returns
630/// `Vec<DeletedWorkspace>`, newest first. Staff only.
631///
632/// A workspace an owner deleted, kept until `purge_after` for staff to
633/// restore.
634#[derive(Clone, Debug, Serialize, Deserialize)]
635#[serde(rename_all = "camelCase")]
636pub struct DeletedWorkspace {
637 pub workspace_id: String,
638 pub slug: String,
639 pub name: String,
640 /// RFC 3339.
641 pub deleted_at: String,
Merge sudo: delete an account with the workspaces it alone owns, purge each642 /// The username of the owner who deleted it, or the staff member (by
643 /// email) who deleted it with the account that was its only owner.
Fixes from the page sweep644 pub deleted_by: String,
645 /// RFC 3339: when it is purged unless restored first.
646 pub purge_after: String,
647 /// What went with it, counted when it was deleted.
648 pub went: WorkspaceDeletion,
649 /// Whether staff can still restore it.
650 pub restorable: bool,
651}
652
653/// `admin_restore_workspace` and `admin_purge_workspace`: staff restore a
654/// deleted workspace within [`WORKSPACE_RESTORE_DAYS`], or purge it now.
655/// `staff` is who, for the audit logs. Purging needs `confirm`, the slug
656/// typed out, and is refused for a protected workspace. Restoring publishes
657/// `workspace.restored`; purging, `workspace.deleted`. Both return
658/// `Outcome<bool>`.
659#[derive(Debug, Serialize, Deserialize)]
660#[serde(rename_all = "camelCase")]
661pub struct AdminDeletedWorkspaceArgs {
662 pub workspace_id: String,
663 pub staff: String,
664 #[serde(default)]
665 pub confirm: String,
666}
667
668/// `transfer_repo_scopes`: a repository moved from `from` to `to`; the
669/// tokens of agents at work on it are kept pointing at it. For repos'
670/// `transfer`. Returns `bool`.
671#[derive(Debug, Serialize, Deserialize)]
672pub struct TransferRepoScopesArgs {
673 pub from: crate::repos::RepoPath,
674 pub to: crate::repos::RepoPath,
675}
676
677/// How long a workspace's old slug keeps redirecting to it, and stays
678/// reserved for it, after a rename.
679pub const SLUG_HOLD_DAYS: u64 = 90;
680
681/// How long a workspace must wait between renames.
682pub const RENAME_COOLDOWN_HOURS: u64 = 24;
683
684// `resolve_slug` takes `SlugArgs` and returns `Option<String>`: the
685// workspace's current slug when `slug` is one it was renamed from within
686// the last `SLUG_HOLD_DAYS`, and null otherwise (including for a slug that
Merge branch 'worktree-agent-a8385d293d42c913a'687// is in use), or the workspace's slug when `slug` is one of its aliases.
688
689// `resolve_alias` takes `SlugArgs` and returns `Option<String>`: the slug
690// now of the workspace `slug` is an alias of, and null when it is none.
691// Aliases are set by g1t's staff only: `g1t` is Flagon, Inc.'s `flagon-io`.
692// An alias follows its workspace through renames.
693
694/// `admin_aliases` takes no arguments (`{}`) and returns
695/// `Vec<WorkspaceAlias>`, by alias. Staff only.
696///
697/// A name staff point at a workspace, so that its addresses (pages, git,
698/// the API, packages) lead to the workspace under its own name.
699#[derive(Clone, Debug, Serialize, Deserialize)]
700#[serde(rename_all = "camelCase")]
701pub struct WorkspaceAlias {
702 pub alias: String,
703 pub workspace_id: String,
704 /// The workspace's slug and name now.
705 pub workspace: String,
706 pub workspace_name: String,
707 /// Why it exists, as staff wrote it.
708 pub note: String,
709 /// The staff member who set it, or `migration`.
710 pub created_by: String,
711 /// RFC 3339.
712 pub created_at: String,
713}
714
715/// `admin_set_alias`: points `alias` at the workspace whose slug is
716/// `workspace`. The alias must have a namespace's shape, must not be one of
717/// the site's routes, and must not be anyone's username, a workspace's slug
718/// (deleted, or held after a rename) or another alias. `note` is required:
719/// it is the reason, kept with the alias and in sudo's audit log. Staff
720/// only. Returns `Outcome<WorkspaceAlias>`.
721#[derive(Debug, Serialize, Deserialize)]
722#[serde(rename_all = "camelCase")]
723pub struct AdminSetAliasArgs {
724 pub alias: String,
725 pub workspace: String,
726 pub note: String,
727 pub staff: String,
728}
729
730/// `admin_remove_alias`: the alias stops leading anywhere, and the name is
731/// nobody's again unless it is reserved. `reason` goes in sudo's audit log.
732/// Staff only. Returns `Outcome<bool>`.
733#[derive(Debug, Serialize, Deserialize)]
734#[serde(rename_all = "camelCase")]
735pub struct AdminRemoveAliasArgs {
736 pub alias: String,
737 pub reason: String,
738 pub staff: String,
739}
Fixes from the page sweep740
741/// `set_workspace_avatar`: owners only. `image` is the file's bytes in
742/// base64: PNG, JPEG, WebP or GIF, at most `MAX_AVATAR_BYTES`. Null removes
743/// the icon. Returns `Outcome<Workspace>`.
744#[derive(Debug, Serialize, Deserialize)]
745pub struct SetWorkspaceAvatarArgs {
746 pub actor: User,
747 pub slug: String,
748 pub image: Option<String>,
749}
750
751/// `set_user_avatar`: a person's own avatar, as `SetWorkspaceAvatarArgs`.
752/// Returns `Outcome<Option<String>>`: the new avatar, or null once removed.
753#[derive(Debug, Serialize, Deserialize)]
754pub struct SetUserAvatarArgs {
755 pub user: User,
756 pub image: Option<String>,
757}
758
759/// The largest avatar that can be uploaded, in bytes.
760pub const MAX_AVATAR_BYTES: usize = 1024 * 1024;
761
762/// `list_workspace_tokens`: members only. Returns
763/// `Outcome<Vec<AccessToken>>`.
764#[derive(Debug, Serialize, Deserialize)]
765pub struct WorkspaceTokensArgs {
766 pub slug: String,
767 pub viewer: crate::Viewer,
768}
769
770/// `remove_workspace_token`: owners only. Returns `Outcome<bool>`.
771#[derive(Debug, Serialize, Deserialize)]
772pub struct RemoveWorkspaceTokenArgs {
773 pub actor: User,
774 pub slug: String,
775 pub id: String,
776}
777
778/// `oauth_authorize`: the signed-in person approved an application. The
779/// caller has checked the client and that it may be redirected to
780/// `redirect_uri`. Returns `OAuthCode`.
781#[derive(Debug, Serialize, Deserialize)]
782#[serde(rename_all = "camelCase")]
783pub struct OAuthAuthorizeArgs {
784 pub user: User,
785 pub client_id: String,
786 /// Shown wherever the application's access is listed.
787 pub client_name: String,
788 pub redirect_uri: String,
789 /// PKCE challenge, method S256.
790 pub code_challenge: String,
791 /// What the person granted, as `resource:level`. Null: full access.
792 #[serde(default)]
793 pub scopes: Option<Vec<String>>,
794}
795
796#[derive(Debug, Serialize, Deserialize)]
797pub struct OAuthCode {
798 pub code: String,
799}
800
801/// `oauth_exchange`: redeems an authorization code.
802/// Returns `Outcome<OAuthTokens>`.
803#[derive(Debug, Serialize, Deserialize)]
804#[serde(rename_all = "camelCase")]
805pub struct OAuthExchangeArgs {
806 pub code: String,
807 pub code_verifier: String,
808 pub client_id: String,
809 pub redirect_uri: String,
810}
811
812/// `oauth_refresh`: trades a refresh token for new tokens.
813/// Returns `Outcome<OAuthTokens>`.
814#[derive(Debug, Serialize, Deserialize)]
815#[serde(rename_all = "camelCase")]
816pub struct OAuthRefreshArgs {
817 pub refresh_token: String,
818 pub client_id: String,
819}
820
821#[derive(Debug, Serialize, Deserialize)]
822#[serde(rename_all = "camelCase")]
823pub struct OAuthTokens {
824 pub access_token: String,
825 /// Works once; using it returns the next one.
826 pub refresh_token: String,
827 /// Seconds until the access token stops working.
828 pub expires_in: u64,
829 /// The scopes granted, space-separated, or `*` for full access.
830 #[serde(default)]
831 pub scope: Option<String>,
832}
833
834/// An application a person has signed in to. Listed by `list_oauth_grants`
835/// and ended by `revoke_oauth_grant`.
836#[derive(Debug, Serialize, Deserialize)]
837#[serde(rename_all = "camelCase")]
838pub struct OAuthGrant {
839 pub id: String,
840 pub client_name: String,
841 /// RFC 3339.
842 pub created_at: String,
843 /// RFC 3339.
844 pub last_used_at: String,
845 /// What the person granted. Null: full access.
846 #[serde(default)]
847 pub scopes: Option<Vec<String>>,
848 /// Signed in before applications were given scopes: full access until
849 /// someone narrows it.
850 #[serde(default)]
851 pub legacy: bool,
852}
853
854/// `update_oauth_grant`: changes what an application the person signed in
855/// to may do, at once and when it refreshes. Returns `Outcome<OAuthGrant>`.
856#[derive(Debug, Serialize, Deserialize)]
857pub struct UpdateOAuthGrantArgs {
858 pub user: User,
859 pub id: String,
860 #[serde(default)]
861 pub scopes: Option<Vec<String>>,
862}
863
864
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.865/// What a scoped token may do: these operations, in this repository. Run
866/// credentials (a token for one CI or sandbox run, see
867/// [`crate::credentials`]) carry one today, and so do tokens g1t's retired
868/// hosted agents were issued, which still parse so their stored rows and
869/// the `@g1t` history they wrote read as before.
Fixes from the page sweep870#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
871pub struct AgentScope {
872 pub repo: crate::repos::RepoPath,
873 /// API and MCP operation names, such as `create_issue`.
874 pub operations: Vec<String>,
875 /// Set on a run credential: the run it belongs to, and what it may do
876 /// with git. See [`crate::credentials`].
877 #[serde(default, skip_serializing_if = "Option::is_none")]
878 pub run: Option<crate::credentials::RunBinding>,
879}
880
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.881/// The id g1t's retired hosted agent acted under. Only ever stored, never
882/// shown: rows it wrote keep it, and still render as g1t's
883/// ([`crate::system::ID`]) work without counting as g1t itself, such as
884/// for whether an approval counts.
Fixes from the page sweep885pub const AGENT_ID: &str = "usr_g1t_agent";
886/// The name g1t's agent is shown by: g1t's own, [`crate::system::USERNAME`].
887/// Everything it does, people see g1t do.
888pub const AGENT_NAME: &str = crate::system::USERNAME;
889
890// --- Staff ---------------------------------------------------------------
891//
892// Staff-only methods, for sudo.g1t.sh. They take no viewer and check no
893// membership: only sudo calls them, over its service binding, after it has
894// verified a Cloudflare Access sign-in and its staff list. Nothing a
895// customer can reach should ever forward to them.
896
897/// `notify_owners`: emails a short notice, with one link, to each owner of
898/// a workspace with a confirmed address. Called by other services (billing
899/// warns owners near their usage limit), never on a person's behalf.
900/// Returns how many were sent.
901#[derive(Clone, Debug, Serialize, Deserialize)]
902pub struct NotifyOwnersArgs {
903 pub workspace: String,
904 pub subject: String,
905 /// One or two sentences: what happened and what it means.
906 pub intro: String,
907 /// The button's words, such as `Open billing`.
908 pub action: String,
909 /// Where the button goes; must be on g1t.sh.
910 pub link: String,
911 /// Small print: why they got it.
912 pub footer: String,
913}
914
915/// `admin_workspaces`: every workspace, newest first, at most
916/// [`ADMIN_WORKSPACES_LIMIT`], optionally only those whose slug, name or
917/// an owner's username or email contains `query`. Returns
918/// `Vec<AdminWorkspace>`. Staff only.
919#[derive(Debug, Default, Serialize, Deserialize)]
920pub struct AdminWorkspacesArgs {
921 #[serde(default)]
922 pub query: Option<String>,
923}
924
925/// The most workspaces one `admin_workspaces` call returns.
926pub const ADMIN_WORKSPACES_LIMIT: usize = 500;
927
928/// An owner of a workspace, as staff see them.
929#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
930pub struct AdminOwner {
931 pub username: String,
932 pub email: Option<String>,
933}
934
935/// A workspace as staff see it: who owns it and how many belong to it.
936#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
937#[serde(rename_all = "camelCase")]
938pub struct AdminWorkspace {
939 pub slug: String,
940 pub name: String,
941 /// RFC 3339.
942 pub created_at: String,
943 pub owners: Vec<AdminOwner>,
944 pub member_count: u32,
945}
946
947/// `admin_workspace`: one workspace with every member, or null. Takes
948/// `SlugArgs`; returns `Option<AdminWorkspaceDetail>`. Staff only.
949#[derive(Clone, Debug, Serialize, Deserialize)]
950#[serde(rename_all = "camelCase")]
951pub struct AdminWorkspaceDetail {
952 pub slug: String,
953 pub name: String,
954 pub description: Option<String>,
955 /// RFC 3339.
956 pub created_at: String,
957 /// Owners first, then by username.
958 pub members: Vec<AdminMember>,
959 /// It can never be deleted ([`protected_names`]).
960 #[serde(default)]
961 pub protected: bool,
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.962 /// Set while staff have it suspended.
963 #[serde(default)]
964 pub suspended: Option<Suspension>,
Fixes from the page sweep965}
966
967/// A member of a workspace, as staff see them.
968#[derive(Clone, Debug, Serialize, Deserialize)]
969pub struct AdminMember {
970 pub username: String,
971 pub email: Option<String>,
972 pub role: crate::Role,
973 /// When they joined the workspace. RFC 3339.
974 pub joined: String,
975}
976
977// --- Profiles ------------------------------------------------------------
978//
979// A person's public page at `g1t.sh/u/<username>`. Everything in a
980// `Profile` is shown to anyone, signed in or not; an email address never is.
981
982/// The most characters each profile field takes.
983pub const MAX_PROFILE_NAME: usize = 80;
984pub const MAX_PROFILE_BIO: usize = 160;
985pub const MAX_PROFILE_LOCATION: usize = 80;
986pub const MAX_PROFILE_WEBSITE: usize = 200;
987pub const MAX_PROFILE_PRONOUNS: usize = 40;
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)988pub const MAX_PROFILE_TIMEZONE: usize = 64;
Fixes from the page sweep989
990/// What anyone may see about a person.
991#[derive(Clone, Debug, Default, Serialize, Deserialize)]
992#[serde(rename_all = "camelCase")]
993pub struct Profile {
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers994 /// Lowercased: what the profile is found and linked by.
Fixes from the page sweep995 pub username: String,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers996 /// The username as its owner wrote it, when that differs: what the page shows.
997 #[serde(default, skip_serializing_if = "Option::is_none")]
998 pub display_username: Option<String>,
Fixes from the page sweep999 /// The name they go by, if they gave one.
1000 pub name: Option<String>,
1001 /// One or two lines about them, at most [`MAX_PROFILE_BIO`] characters.
1002 pub bio: Option<String>,
1003 pub location: Option<String>,
1004 /// An `https://` address.
1005 pub website: Option<String>,
1006 pub pronouns: Option<String>,
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1007 /// The time zone they are in, an IANA name such as `America/Denver`.
1008 #[serde(default)]
1009 pub timezone: Option<String>,
Fixes from the page sweep1010 /// The uploaded avatar's hash, served at `/avatars/<avatar>`.
1011 pub avatar: Option<String>,
1012 /// When the account was made. RFC 3339.
1013 pub created_at: String,
1014}
1015
1016// `profile` takes `UsernameArgs` and returns `Option<Profile>`: null for
1017// an account that does not exist.
1018
1019/// `update_profile`: a person changes their own profile. Every field is
1020/// replaced; an empty one is cleared. Returns `Outcome<Profile>`.
1021#[derive(Debug, Default, Serialize, Deserialize)]
1022#[serde(rename_all = "camelCase")]
1023pub struct UpdateProfileArgs {
1024 pub actor: User,
1025 #[serde(default)]
1026 pub name: String,
1027 #[serde(default)]
1028 pub bio: String,
1029 #[serde(default)]
1030 pub location: String,
1031 #[serde(default)]
1032 pub website: String,
1033 #[serde(default)]
1034 pub pronouns: String,
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1035 /// An IANA time zone name, such as `America/Denver`.
1036 #[serde(default)]
1037 pub timezone: String,
Fixes from the page sweep1038}
1039
1040/// `profile_workspaces`: the workspaces shown on a person's profile, as
1041/// `viewer` may see them. A membership is shown only when it is no secret
1042/// from the viewer: a workspace the viewer belongs to as well, or one of
1043/// `public`, the workspaces the caller found the person has made a public
1044/// project in (whose page shows that already). Returns
1045/// `Vec<ProfileWorkspace>`; empty for an account that does not exist.
1046#[derive(Debug, Serialize, Deserialize)]
1047pub struct ProfileWorkspacesArgs {
1048 pub username: String,
1049 pub viewer: crate::Viewer,
1050 #[serde(default)]
1051 pub public: Vec<String>,
1052}
1053
1054/// A workspace on a person's profile.
1055#[derive(Clone, Debug, Serialize, Deserialize)]
1056pub struct ProfileWorkspace {
1057 pub slug: String,
1058 pub name: String,
1059 pub avatar: Option<String>,
1060}
1061
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.1062// --- Dock pins -------------------------------------------------------------
1063//
1064// The apps a person pins to their dock in a workspace, kept with their
1065// account so the dock follows them to every device. Each person's own:
1066// nobody else reads or sets them.
1067
1068/// The most apps a person pins in one workspace.
1069pub const MAX_DOCK_PINS: usize = 24;
1070/// The most characters an app key takes.
1071pub const MAX_DOCK_APP_KEY: usize = 32;
1072
1073/// `dock_pins`: the apps `user` pinned in the workspace `workspace` (a
1074/// slug), in the order they set. Returns `Option<Vec<String>>`: null when
1075/// they never saved any there, or are not one of its members.
1076#[derive(Debug, Default, Serialize, Deserialize)]
1077#[serde(rename_all = "camelCase")]
1078pub struct DockPinsArgs {
1079 pub user: User,
1080 pub workspace: String,
1081}
1082
1083/// `set_dock_pins`: replaces `user`'s pins in `workspace` with `apps`, in
1084/// that order. Each key is lowercase letters, digits and hyphens, at most
1085/// [`MAX_DOCK_APP_KEY`] characters; a repeat is dropped; at most
1086/// [`MAX_DOCK_PINS`]. Which keys name real apps is the web app's to say.
1087/// Returns `Outcome<Vec<String>>`: the pins as saved.
1088#[derive(Debug, Default, Serialize, Deserialize)]
1089#[serde(rename_all = "camelCase")]
1090pub struct SetDockPinsArgs {
1091 pub user: User,
1092 pub workspace: String,
1093 #[serde(default)]
1094 pub apps: Vec<String>,
1095}
1096
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.1097// --- Sounds and desktop notifications --------------------------------------
1098//
1099// What a person hears when chat moves, kept with their account so a phone,
1100// a laptop and the desktop app all sound the same. Which sound plays when
1101// is the web app's rule (apps/web/app/lib/chat-sounds.ts); identity keeps
1102// the choices. Do not disturb is not here: it is the person's presence
1103// (`dnd_until`, services/notify), which also silences pop-ups and pushes.
1104// Mirrors `SoundSettings` in packages/contracts/src/identity.ts.
1105
1106/// The sets of sounds a person can choose from.
1107pub const SOUND_SETS: [&str; 2] = ["soft", "bright"];
1108/// The cues a person can turn on and off. `call` is reserved and has no setting.
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.1109pub const SOUND_CUES: [&str; 4] = ["message", "direct", "mention", "sent"];
1110/// Cues that no longer play: `agent_done`, from g1t's retired hosted
1111/// agents. Saved settings that name one still read, without it, and a
1112/// change that names one leaves it out rather than failing.
1113pub const RETIRED_SOUND_CUES: [&str; 1] = ["agent_done"];
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.1114/// Before anyone changes them: every cue but the tick as you send.
1115pub const SOUND_CUES_OFF_BY_DEFAULT: [&str; 1] = ["sent"];
1116pub const DEFAULT_SOUND_SET: &str = "soft";
1117pub const DEFAULT_SOUND_VOLUME: u8 = 60;
1118
1119/// A person's sound settings, as kept and as answered. Wire fields are
1120/// snake_case, as the site's JSON is.
1121#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1122pub struct SoundSettings {
1123 /// Whether anything plays at all.
1124 pub sounds_enabled: bool,
1125 /// One of [`SOUND_SETS`].
1126 pub sound_set: String,
1127 /// 0 to 100.
1128 pub sound_volume: u8,
1129 /// Each cue on or off, by name; a cue left out is on, except `sent`.
1130 #[serde(default)]
1131 pub sound_cues: std::collections::BTreeMap<String, bool>,
1132 /// Whether an open tab shows a system notification for a message to
1133 /// you while its window is not in front.
1134 pub desktop_toasts: bool,
1135}
1136
1137impl Default for SoundSettings {
1138 fn default() -> Self {
1139 SoundSettings {
1140 sounds_enabled: true,
1141 sound_set: DEFAULT_SOUND_SET.to_owned(),
1142 sound_volume: DEFAULT_SOUND_VOLUME,
1143 sound_cues: SOUND_CUES
1144 .iter()
1145 .map(|cue| ((*cue).to_owned(), !SOUND_CUES_OFF_BY_DEFAULT.contains(cue)))
1146 .collect(),
1147 desktop_toasts: false,
1148 }
1149 }
1150}
1151
1152/// A change to them: only what is sent changes; `sound_cues` merges by cue.
1153#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1154pub struct SoundSettingsChange {
1155 #[serde(default)]
1156 pub sounds_enabled: Option<bool>,
1157 #[serde(default)]
1158 pub sound_set: Option<String>,
1159 #[serde(default)]
1160 pub sound_volume: Option<i64>,
1161 #[serde(default)]
1162 pub sound_cues: Option<std::collections::BTreeMap<String, bool>>,
1163 #[serde(default)]
1164 pub desktop_toasts: Option<bool>,
1165}
1166
1167/// `sound_settings`: `user`'s own, or the defaults when they never changed
1168/// them. Returns `SoundSettings`. People only.
1169#[derive(Debug, Default, Serialize, Deserialize)]
1170#[serde(rename_all = "camelCase")]
1171pub struct SoundSettingsArgs {
1172 pub user: User,
1173}
1174
1175/// `set_sound_settings`: changes `user`'s own. Returns
1176/// `Outcome<SoundSettings>`: the settings as kept, or `invalid` for a set
1177/// or cue g1t does not have, or a volume outside 0 to 100.
1178#[derive(Debug, Default, Serialize, Deserialize)]
1179#[serde(rename_all = "camelCase")]
1180pub struct SetSoundSettingsArgs {
1181 pub user: User,
1182 #[serde(default)]
1183 pub change: SoundSettingsChange,
1184}
1185
Fixes from the page sweep1186/// `directory`: every account or every workspace, as their public pages
1187/// show them, a page at a time in name order. For services that index
1188/// them, such as search; nothing private is in it. Returns
1189/// `DirectoryPage`.
1190#[derive(Debug, Default, Serialize, Deserialize)]
1191pub struct DirectoryArgs {
1192 /// `user` or `workspace`.
1193 pub kind: String,
1194 /// Names after this one.
1195 #[serde(default)]
1196 pub after: Option<String>,
1197 pub limit: u32,
1198}
1199
1200/// One account or workspace in the directory.
1201#[derive(Clone, Debug, Serialize, Deserialize)]
1202#[serde(rename_all = "camelCase")]
1203pub struct DirectoryEntry {
1204 /// The account's or workspace's id.
1205 pub id: String,
1206 /// A username or a workspace's slug.
1207 pub slug: String,
1208 /// A person's display name or a workspace's name.
1209 pub name: Option<String>,
1210 /// A person's bio or a workspace's description.
1211 pub bio: Option<String>,
1212 pub avatar: Option<String>,
1213 /// RFC 3339.
1214 pub created_at: String,
1215}
1216
1217#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1218pub struct DirectoryPage {
1219 pub entries: Vec<DirectoryEntry>,
1220 /// Where the next page starts; null on the last.
1221 pub next: Option<String>,
1222}
1223
1224// --- Invites ---------------------------------------------------------------
1225//
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.1226// An invitation into a workspace or onto a repository, sent to an address
1227// without an account, carries an invite code: registering with it applies
1228// the invitation once the address is confirmed. Anyone may register
1229// without one. See services/identity/src/invites.rs.
Fixes from the page sweep1230
1231/// How long an invite works, unless identity's `INVITE_TTL_DAYS` var says
1232/// otherwise.
1233pub const INVITE_TTL_DAYS: u64 = 30;
1234
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)1235/// Where an invite stands. Only a pending invite can be used. A pending
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.1236/// invite can be revoked, and so can one awaiting confirmation.
Fixes from the page sweep1237#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1238#[serde(rename_all = "snake_case")]
1239pub enum InviteStatus {
1240 Pending,
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)1241 /// Used to make an account that has not confirmed its email address
1242 /// yet. The code is spent; the workspace (or repository) it gives is
1243 /// joined when the address is confirmed, unless it is revoked first.
1244 AwaitingConfirmation,
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1245 /// Used to make an account that has confirmed its address, for a
1246 /// workspace it has not yet joined or declined: the workspace
1247 /// invitation waits for the person's answer (`accept_invitation`).
1248 AwaitingAnswer,
Fixes from the page sweep1249 Redeemed,
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1250 /// Its person declined the workspace it invited them to.
1251 Declined,
Fixes from the page sweep1252 Expired,
1253 Revoked,
1254}
1255
1256/// What using an invite does.
1257#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1258#[serde(rename_all = "snake_case")]
1259pub enum InviteKind {
1260 /// Makes a new account, and joins `workspace` when one is set.
1261 Account,
1262 /// An existing account joins `workspace`. Never makes an account.
1263 Workspace,
1264}
1265
1266/// One invite, as the person who made it sees it.
1267#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1268#[serde(rename_all = "camelCase")]
1269pub struct Invite {
1270 pub id: String,
1271 /// The code, such as `g1t-k7m2-q9xd-…`: returned once when the invite
1272 /// is made, and afterwards to whoever made it while it is pending.
1273 /// Null otherwise.
1274 pub code: Option<String>,
1275 /// The code's first group, such as `g1t-k7m2`, to recognise it by.
1276 pub hint: String,
1277 /// Only an account with this address can use it. Null: anyone with
1278 /// the code.
1279 pub email: Option<String>,
1280 pub kind: InviteKind,
1281 /// The workspace it joins, by slug.
1282 pub workspace: Option<String>,
1283 pub status: InviteStatus,
1284 /// Who made it, by username. Null when g1t staff did.
1285 pub invited_by: Option<String>,
1286 /// The account that used it, by username.
1287 pub redeemed_by: Option<String>,
1288 /// RFC 3339.
1289 pub created_at: String,
1290 /// RFC 3339.
1291 pub expires_at: String,
1292 /// RFC 3339.
1293 pub redeemed_at: Option<String>,
1294 /// RFC 3339.
1295 pub revoked_at: Option<String>,
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1296 /// The account a workspace invitation is for, by username: someone
1297 /// invited by username, or the account the invite made.
1298 #[serde(default)]
1299 pub invitee: Option<String>,
1300 /// The role `workspace` is joined with. Null when it names none.
1301 #[serde(default)]
1302 pub role: Option<crate::Role>,
Fixes from the page sweep1303 /// The staff member who minted it. Only in staff views.
1304 #[serde(default, skip_serializing_if = "Option::is_none")]
1305 pub staff: Option<String>,
1306}
1307
1308/// `check_invite`: what an invite code is for, before using it. Returns
1309/// `Outcome<InvitePreview>`; a code that is unknown, used, revoked or
1310/// expired gets the same answer, so codes cannot be probed. With
1311/// `any_status`, a real code that can no longer be used is described
1312/// instead (its `status` says why), so the page can say whom to ask for a
1313/// new one; an unknown code still gets the one answer.
1314#[derive(Debug, Serialize, Deserialize)]
1315pub struct InviteCodeArgs {
1316 pub code: String,
1317 /// Who is asking, such as the visitor's IP address, for rate limits.
1318 #[serde(default)]
1319 pub client: Option<String>,
1320 /// Who is looking, if signed in: sets `InvitePreview::for_viewer`.
1321 #[serde(default)]
1322 pub viewer: Option<User>,
1323 #[serde(default)]
1324 pub any_status: bool,
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm1325 /// The `proof` from the invite email's link, if the page was opened
1326 /// from it: sets `InvitePreview::email_proven`.
1327 #[serde(default)]
1328 pub email_proof: Option<String>,
Fixes from the page sweep1329}
1330
1331/// Someone shown on an invite.
1332#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1333pub struct InviteFrom {
1334 pub username: String,
1335 pub name: Option<String>,
1336 pub avatar: Option<String>,
1337}
1338
1339/// A repository an invite code was sent with: using the code accepts the
1340/// invitation to collaborate on it.
1341#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1342pub struct InviteRepository {
1343 /// `workspace/repo`.
1344 pub name: String,
1345 /// The role it gives, such as `write`.
1346 pub role: String,
1347}
1348
1349/// What a valid invite code is for.
1350#[derive(Clone, Debug, Serialize, Deserialize)]
1351#[serde(rename_all = "camelCase")]
1352pub struct InvitePreview {
1353 pub kind: InviteKind,
1354 /// Pending, unless `any_status` asked about a code that is spent.
1355 pub status: InviteStatus,
1356 /// Null when g1t staff sent it.
1357 pub invited_by: Option<InviteFrom>,
1358 pub workspace: Option<ProfileWorkspace>,
1359 /// The repository it accepts an invitation to, if it was sent with one.
1360 pub repository: Option<InviteRepository>,
1361 /// The address it is for, partly hidden, such as `a•••@example.com`.
1362 pub email: Option<String>,
1363 /// The address in full, while it is pending: whoever holds the code
1364 /// was sent it there. Fills in and locks the sign-up form.
1365 pub address: Option<String>,
1366 /// Whether the address it is for has a g1t account already, so the
1367 /// page asks them to sign in rather than sign up.
1368 pub has_account: bool,
1369 /// With a viewer: whether the invite is theirs (it is for one of their
1370 /// confirmed addresses, or they used it). Null without a viewer or,
1371 /// for a pending invite, when it is for anyone with the code.
1372 pub for_viewer: Option<bool>,
1373 /// RFC 3339.
1374 pub expires_at: String,
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm1375 /// Whether `email_proof` was this pending invite's own, from the email
1376 /// it was sent in: the account made with it starts with `address`
1377 /// confirmed. False without a proof, with a wrong one, and for an
1378 /// invite bound to no address.
1379 #[serde(default)]
1380 pub email_proven: bool,
Fixes from the page sweep1381}
1382
1383/// `accept_invite`: a signed-in person uses a workspace invite made for
1384/// their confirmed address, and joins the workspace, or an invite sent with
1385/// a repository invitation, and accepts it. Returns `Outcome<String>`: the
1386/// workspace's slug, or `workspace/repo`.
1387#[derive(Debug, Serialize, Deserialize)]
1388pub struct AcceptInviteArgs {
1389 pub user: User,
1390 pub code: String,
1391}
1392
1393/// `invite_member`: an owner invites an email address into a workspace.
1394/// It always makes an invite bound to that address and emails it, so the
1395/// answer never says whether the address has an account. Without one, the
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.1396/// invite registers and joins in one step. Returns `Outcome<Invite>`, with
1397/// the code.
Fixes from the page sweep1398#[derive(Debug, Serialize, Deserialize)]
1399pub struct InviteMemberArgs {
1400 pub actor: User,
1401 pub slug: String,
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1402 /// An email address. Give this or `username`.
1403 #[serde(default)]
Fixes from the page sweep1404 pub email: String,
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1405 /// A g1t username: that account gets a workspace invitation to accept
1406 /// or decline, in its inbox and by email. Nobody joins without saying
1407 /// yes.
1408 #[serde(default)]
1409 pub username: Option<String>,
1410 /// The role they join with; member when absent.
1411 #[serde(default)]
1412 pub role: Option<crate::Role>,
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.1413 /// A note from whoever invites, quoted in the invitation email. Up to
1414 /// [`MAX_INVITE_MESSAGE`] characters; longer is cut, blank is none.
1415 #[serde(default)]
1416 pub message: Option<String>,
Fixes from the page sweep1417 /// Where the request came in, for the audit log; g1t.sh when absent.
1418 #[serde(default)]
1419 pub surface: Option<crate::audit::Surface>,
1420}
1421
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.1422/// The most characters an invitation's note keeps.
1423pub const MAX_INVITE_MESSAGE: usize = 500;
1424
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)1425/// A workspace invitation waiting for its person's answer, as they see it.
1426/// `list_invitations` (takes `UserArgs`) returns `Vec<WorkspaceInvitation>`,
1427/// newest first: pending ones only, never expired, revoked or answered.
1428#[derive(Clone, Debug, Serialize, Deserialize)]
1429#[serde(rename_all = "camelCase")]
1430pub struct WorkspaceInvitation {
1431 pub id: String,
1432 pub workspace: ProfileWorkspace,
1433 /// The role accepting joins with.
1434 pub role: crate::Role,
1435 /// Null when g1t staff sent it.
1436 pub invited_by: Option<InviteFrom>,
1437 /// RFC 3339.
1438 pub created_at: String,
1439 /// RFC 3339.
1440 pub expires_at: String,
1441}
1442
1443/// `accept_invitation`: the person it is for joins the workspace with the
1444/// role it names. Returns `Outcome<String>`, the workspace's slug.
1445///
1446/// `decline_invitation`: they say no; whoever sent it is told in their
1447/// inbox. Returns `Outcome<bool>`.
1448#[derive(Debug, Serialize, Deserialize)]
1449pub struct InvitationArgs {
1450 pub user: User,
1451 pub id: String,
1452 /// Where the request came in, for the audit log; g1t.sh when absent.
1453 #[serde(default)]
1454 pub surface: Option<crate::audit::Surface>,
1455}
1456
1457/// `find_people`: accounts whose username starts with `query`, or whose
1458/// name contains it, for picking someone to invite. Only what a profile
1459/// shows: a username, a name and an avatar, never an email address.
1460/// Returns `Vec<InviteFrom>`, at most `limit` (10 at most, 8 when absent).
1461#[derive(Debug, Serialize, Deserialize)]
1462pub struct FindPeopleArgs {
1463 pub query: String,
1464 #[serde(default)]
1465 pub limit: Option<u32>,
1466}
1467
Fixes from the page sweep1468/// `workspace_invites` (takes `ListMembersArgs`): a workspace's invites,
1469/// newest first. Owners only. Returns `Outcome<Vec<Invite>>`.
1470///
1471/// `revoke_workspace_invite`: owners only. Returns `Outcome<Invite>`.
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.1472///
1473/// `resend_workspace_invite`: owners only. Sends a pending invitation
1474/// again, by email to the address it is bound to or the invited account's
1475/// confirmed address, and again in that account's inbox. The invitation
1476/// itself is unchanged: same code, same day it stops working. Returns
1477/// `Outcome<Invite>`; one that is not pending answers `409`.
Fixes from the page sweep1478#[derive(Debug, Serialize, Deserialize)]
1479pub struct WorkspaceInviteArgs {
1480 pub actor: User,
1481 pub slug: String,
1482 pub id: String,
1483}
1484
1485// --- Invites, staff only ---
1486
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.1487/// The most rows one staff listing of invites returns.
Fixes from the page sweep1488pub const ADMIN_INVITES_LIMIT: usize = 500;
1489
1490/// `admin_invites`: every invite, newest first, at most
1491/// [`ADMIN_INVITES_LIMIT`], optionally only those whose code starts with
1492/// `query`, or whose email, inviter or redeemer contains it. Returns
1493/// `Vec<Invite>`.
1494#[derive(Debug, Default, Serialize, Deserialize)]
1495pub struct AdminInvitesArgs {
1496 #[serde(default)]
1497 pub query: Option<String>,
1498}
1499
1500/// `admin_revoke_invite`: revokes any pending invite. Returns
1501/// `Outcome<Invite>`.
1502#[derive(Debug, Serialize, Deserialize)]
1503pub struct AdminRevokeInviteArgs {
1504 pub id: String,
1505 pub staff: String,
1506}
1507
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.1508// --- Suspension and blocked email domains, staff only ---
1509//
1510// See services/identity/src/suspension.rs. A suspended account cannot sign
1511// in, and its sessions and tokens stop working at once; a suspended
1512// workspace's own tokens stop working, and sudo also puts a hold on its
1513// compute (billing), which stops its runs, deploys, new self-hosted
1514// runners and telemetry. Both are undone the same way. A blocked email
1515// domain makes no new accounts and confirms no new addresses.
Fixes from the page sweep1516
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.1517/// Why and since when staff suspended an account or a workspace.
1518#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1519#[serde(rename_all = "camelCase")]
1520pub struct Suspension {
1521 pub reason: String,
1522 /// RFC 3339.
1523 pub at: String,
1524 /// The staff member, by email.
1525 pub by: String,
Fixes from the page sweep1526}
1527
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.1528/// `admin_suspend_user` (`name` a username) and `admin_suspend_workspace`
1529/// (`name` a slug): `suspend` true suspends with `reason` (required),
1530/// false lifts it. Returns `Outcome<Option<Suspension>>`: what stands now.
Fixes from the page sweep1531#[derive(Debug, Serialize, Deserialize)]
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.1532pub struct AdminSuspendArgs {
Fixes from the page sweep1533 pub name: String,
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.1534 pub suspend: bool,
Fixes from the page sweep1535 #[serde(default)]
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.1536 pub reason: String,
Fixes from the page sweep1537 pub staff: String,
1538}
1539
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.1540/// An email domain no new account may sign up with.
1541#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Fixes from the page sweep1542#[serde(rename_all = "camelCase")]
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.1543pub struct BlockedDomain {
1544 /// Lowercased, such as `spam.example`. Its subdomains are blocked too.
1545 pub domain: String,
1546 pub reason: String,
1547 pub blocked_by: String,
Fixes from the page sweep1548 /// RFC 3339.
1549 pub created_at: String,
1550}
1551
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.1552/// `admin_blocked_domains` takes `{}` and returns `Vec<BlockedDomain>`.
Fixes from the page sweep1553///
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.1554/// `admin_block_domain`: `block` true blocks `domain` with `reason`,
1555/// false unblocks it. Returns `Outcome<Vec<BlockedDomain>>`, the list now.
1556#[derive(Debug, Serialize, Deserialize)]
1557pub struct AdminBlockDomainArgs {
1558 pub domain: String,
1559 pub block: bool,
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)1560 #[serde(default)]
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.1561 pub reason: String,
1562 pub staff: String,
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)1563}
1564
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.1565// --- Shared invite links ---
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)1566//
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.1567// Before registration opened, staff handed groups shared invite links.
1568// None are made or used any more; staff still see which one an account
1569// was made with.
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)1570
1571/// The shared invite link an account was made with.
1572#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1573pub struct SharedInviteSource {
1574 pub id: String,
1575 pub label: String,
Fixes from the page sweep1576}
1577
1578#[cfg(test)]
1579mod deletion_tests {
1580 use super::{WorkspaceDeletion, protected_names};
1581
1582 #[test]
1583 fn only_billing_or_protection_stands_in_the_way() {
1584 let clear = WorkspaceDeletion {
1585 repositories: 2,
1586 projects: 1,
1587 members: 3,
1588 ..WorkspaceDeletion::default()
1589 };
1590 assert!(!clear.blocked());
1591 assert_eq!(clear.reason("acme"), None);
1592 let owing = WorkspaceDeletion {
1593 billing: Some("Pay first.".into()),
1594 ..WorkspaceDeletion::default()
1595 };
1596 assert!(owing.blocked());
1597 assert_eq!(owing.reason("acme").as_deref(), Some("Pay first."));
1598 let protected = WorkspaceDeletion {
1599 billing: Some("Pay first.".into()),
1600 protected: true,
1601 ..WorkspaceDeletion::default()
1602 };
1603 assert!(protected.blocked());
1604 assert_eq!(
1605 protected.reason("flagon-io").as_deref(),
1606 Some("flagon-io is protected and can never be deleted.")
1607 );
1608 }
1609
1610 #[test]
1611 fn flagon_is_protected_whatever_the_variable_says() {
1612 assert_eq!(protected_names(None), ["flagon-io"]);
1613 assert_eq!(protected_names(Some("")), ["flagon-io"]);
1614 assert_eq!(protected_names(Some(" , ")), ["flagon-io"]);
1615 assert_eq!(
1616 protected_names(Some("Flagon-IO, acme ,wsp_1")),
1617 ["flagon-io", "acme", "wsp_1"]
1618 );
1619 }
1620}

This file's history is long; its oldest lines are credited to the oldest commit read.