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 sweep | 1 | //! 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 | ||
| 6 | use serde::{Deserialize, Serialize}; | |
| 7 | ||
| 8 | use crate::User; | |
| 9 | ||
| 10 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 11 | #[serde(rename_all = "camelCase")] | |
| 12 | pub 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-a16631325aecf58ca | 18 | /// 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 sweep | 22 | } |
| 23 | ||
| Fine-grained personal tokens, workspace token rules and approvals in identity | 24 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] |
| Fixes from the page sweep | 25 | #[serde(rename_all = "camelCase")] |
| 26 | pub 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 managers | 36 | /// 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 sweep | 38 | #[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 managers | 46 | /// 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 identity | 49 | #[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 managers | 50 | pub permissions: std::collections::BTreeMap<String, String>, |
| Fine-grained personal tokens, workspace token rules and approvals in identity | 51 | /// 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 managers | 54 | /// 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 identity | 72 | #[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 managers | 73 | 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 identity | 79 | #[serde(default, skip_serializing_if = "std::ops::Not::not")] |
| 80 | pub admin: bool, | |
| Merge main into Artifacts Phase 2 | 81 | /// 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 sweep | 85 | } |
| 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)] | |
| 93 | pub 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")] | |
| 103 | pub struct SignedIn { | |
| 104 | pub user: User, | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 105 | /// Empty while `two_factor_challenge` is set: no session is made until |
| 106 | /// the code is given. | |
| Fixes from the page sweep | 107 | pub session_token: String, |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 108 | /// 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 sweep | 113 | } |
| 114 | ||
| 115 | /// `sign_out` and `user_for_session`. | |
| 116 | #[derive(Debug, Serialize, Deserialize)] | |
| 117 | #[serde(rename_all = "camelCase")] | |
| 118 | pub 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)] | |
| 124 | pub struct GitCredentialsArgs { | |
| 125 | pub username: String, | |
| 126 | pub secret: String, | |
| 127 | } | |
| 128 | ||
| 129 | /// `user_for_access_token`. | |
| 130 | #[derive(Debug, Serialize, Deserialize)] | |
| 131 | pub struct TokenArgs { | |
| 132 | pub token: String, | |
| 133 | } | |
| 134 | ||
| Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca | 135 | /// `user_for_ssh_key`, and `principal_for_ssh_key` (see [`crate::deploy_keys`]). |
| Fixes from the page sweep | 136 | #[derive(Debug, Serialize, Deserialize)] |
| 137 | pub struct FingerprintArgs { | |
| 138 | pub fingerprint: String, | |
| 139 | } | |
| 140 | ||
| 141 | /// `user_by_username`. | |
| 142 | #[derive(Debug, Serialize, Deserialize)] | |
| 143 | pub 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-a69aeabc4b0deeb97 | 149 | /// 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 sweep | 151 | #[derive(Debug, Serialize, Deserialize)] |
| 152 | pub 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 calendar | 156 | /// `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)] | |
| 161 | pub struct DisplayUsernamesArgs { | |
| 162 | pub usernames: Vec<String>, | |
| 163 | } | |
| 164 | ||
| Fixes from the page sweep | 165 | /// `list_ssh_keys` and `list_access_tokens`. |
| 166 | #[derive(Debug, Serialize, Deserialize)] | |
| 167 | pub struct UserArgs { | |
| 168 | pub user: User, | |
| 169 | } | |
| 170 | ||
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 171 | /// `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)] | |
| 176 | pub struct SshKeyOwnersArgs { | |
| 177 | pub fingerprints: Vec<String>, | |
| 178 | } | |
| 179 | ||
| Fixes from the page sweep | 180 | /// `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")] | |
| 184 | pub 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)] | |
| 192 | pub 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")] | |
| 202 | pub 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")] | |
| 227 | pub 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")] | |
| 247 | pub struct RevokeJobTokensArgs { | |
| 248 | pub job_id: String, | |
| 249 | } | |
| 250 | ||
| Fixes from the page sweep | 251 | /// The plaintext token is returned once and never stored. |
| 252 | #[derive(Debug, Serialize, Deserialize)] | |
| 253 | pub 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 sweep | 265 | #[derive(Debug, Serialize, Deserialize)] |
| 266 | pub 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 sweep | 272 | #[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 confirm | 274 | /// 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 sweep | 279 | /// 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)] | |
| 286 | pub 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)] | |
| 296 | pub 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)] | |
| 306 | pub 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")] | |
| 314 | pub 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")] | |
| 321 | pub 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")] | |
| 336 | pub struct DeviceLookupArgs { | |
| 337 | pub user_code: String, | |
| 338 | } | |
| 339 | ||
| 340 | #[derive(Debug, Serialize, Deserialize)] | |
| 341 | #[serde(rename_all = "camelCase")] | |
| 342 | pub 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")] | |
| 351 | pub 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")] | |
| 360 | pub 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")] | |
| 367 | pub 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")] | |
| 384 | pub 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 tokens | 404 | /// 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 sweep | 413 | } |
| 414 | ||
| 415 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 416 | pub struct Member { | |
| 417 | pub username: String, | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 418 | /// 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 sweep | 422 | pub role: crate::Role, |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 423 | /// 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 sweep | 430 | /// 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")] | |
| 445 | pub enum DataResidency { | |
| 446 | #[default] | |
| 447 | Anywhere, | |
| 448 | Eu, | |
| 449 | } | |
| 450 | ||
| 451 | impl 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)] | |
| 472 | pub struct SetResidencyArgs { | |
| 473 | pub actor: User, | |
| 474 | pub slug: String, | |
| 475 | pub residency: DataResidency, | |
| 476 | } | |
| 477 | ||
| Fixes from the page sweep | 478 | /// `create_workspace`. Returns `Outcome<Workspace>`. |
| 479 | #[derive(Debug, Serialize, Deserialize)] | |
| 480 | pub 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)] | |
| 489 | pub struct SlugArgs { | |
| 490 | pub slug: String, | |
| 491 | } | |
| 492 | ||
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 493 | /// `list_members`: members only. Owners also see each member's |
| 494 | /// `two_factor`. Returns `Outcome<Vec<Member>>`. | |
| Fixes from the page sweep | 495 | #[derive(Debug, Serialize, Deserialize)] |
| 496 | pub 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 tokens | 505 | /// leaving (`members::LeaveWorkspaceArgs`); removing an owner is refused |
| 506 | /// when they are the last. Each returns `Outcome<bool>`. | |
| Fixes from the page sweep | 507 | #[derive(Debug, Serialize, Deserialize)] |
| 508 | pub struct MemberArgs { | |
| 509 | pub actor: User, | |
| 510 | pub slug: String, | |
| 511 | pub username: String, | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 512 | #[serde(default)] |
| 513 | pub surface: Option<crate::audit::Surface>, | |
| Fixes from the page sweep | 514 | } |
| 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)] | |
| 519 | pub 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")] | |
| 536 | pub 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)] | |
| 559 | pub 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)] | |
| 572 | pub 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 | ||
| 586 | impl 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. | |
| 602 | pub const WORKSPACE_RESTORE_DAYS: u64 = 30; | |
| 603 | ||
| 604 | /// Workspaces nobody can delete, whatever identity's `PROTECTED_WORKSPACES` | |
| 605 | /// says: Flagon's, which runs g1t. | |
| 606 | pub 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. | |
| 612 | pub 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. | |
| 625 | pub 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")] | |
| 636 | pub 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 each | 642 | /// 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 sweep | 644 | 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")] | |
| 661 | pub 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)] | |
| 672 | pub 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. | |
| 679 | pub const SLUG_HOLD_DAYS: u64 = 90; | |
| 680 | ||
| 681 | /// How long a workspace must wait between renames. | |
| 682 | pub 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")] | |
| 701 | pub 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")] | |
| 723 | pub 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")] | |
| 735 | pub struct AdminRemoveAliasArgs { | |
| 736 | pub alias: String, | |
| 737 | pub reason: String, | |
| 738 | pub staff: String, | |
| 739 | } | |
| Fixes from the page sweep | 740 | |
| 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)] | |
| 745 | pub 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)] | |
| 754 | pub struct SetUserAvatarArgs { | |
| 755 | pub user: User, | |
| 756 | pub image: Option<String>, | |
| 757 | } | |
| 758 | ||
| 759 | /// The largest avatar that can be uploaded, in bytes. | |
| 760 | pub const MAX_AVATAR_BYTES: usize = 1024 * 1024; | |
| 761 | ||
| 762 | /// `list_workspace_tokens`: members only. Returns | |
| 763 | /// `Outcome<Vec<AccessToken>>`. | |
| 764 | #[derive(Debug, Serialize, Deserialize)] | |
| 765 | pub 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)] | |
| 772 | pub 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")] | |
| 783 | pub 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)] | |
| 797 | pub 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")] | |
| 805 | pub 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")] | |
| 816 | pub struct OAuthRefreshArgs { | |
| 817 | pub refresh_token: String, | |
| 818 | pub client_id: String, | |
| 819 | } | |
| 820 | ||
| 821 | #[derive(Debug, Serialize, Deserialize)] | |
| 822 | #[serde(rename_all = "camelCase")] | |
| 823 | pub 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")] | |
| 838 | pub 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)] | |
| 857 | pub 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 sweep | 870 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 871 | pub 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 sweep | 885 | pub 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. | |
| 888 | pub 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)] | |
| 902 | pub 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)] | |
| 920 | pub struct AdminWorkspacesArgs { | |
| 921 | #[serde(default)] | |
| 922 | pub query: Option<String>, | |
| 923 | } | |
| 924 | ||
| 925 | /// The most workspaces one `admin_workspaces` call returns. | |
| 926 | pub const ADMIN_WORKSPACES_LIMIT: usize = 500; | |
| 927 | ||
| 928 | /// An owner of a workspace, as staff see them. | |
| 929 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 930 | pub 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")] | |
| 938 | pub 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")] | |
| 951 | pub 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 sweep | 965 | } |
| 966 | ||
| 967 | /// A member of a workspace, as staff see them. | |
| 968 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 969 | pub 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. | |
| 983 | pub const MAX_PROFILE_NAME: usize = 80; | |
| 984 | pub const MAX_PROFILE_BIO: usize = 160; | |
| 985 | pub const MAX_PROFILE_LOCATION: usize = 80; | |
| 986 | pub const MAX_PROFILE_WEBSITE: usize = 200; | |
| 987 | pub 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) | 988 | pub const MAX_PROFILE_TIMEZONE: usize = 64; |
| Fixes from the page sweep | 989 | |
| 990 | /// What anyone may see about a person. | |
| 991 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 992 | #[serde(rename_all = "camelCase")] | |
| 993 | pub 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 managers | 994 | /// Lowercased: what the profile is found and linked by. |
| Fixes from the page sweep | 995 | 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 managers | 996 | /// 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 sweep | 999 | /// 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 sweep | 1010 | /// 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")] | |
| 1023 | pub 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 sweep | 1038 | } |
| 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)] | |
| 1047 | pub 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)] | |
| 1056 | pub 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. | |
| 1069 | pub const MAX_DOCK_PINS: usize = 24; | |
| 1070 | /// The most characters an app key takes. | |
| 1071 | pub 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")] | |
| 1078 | pub 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")] | |
| 1090 | pub 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. | |
| 1107 | pub 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. | 1109 | pub 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. | |
| 1113 | pub 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. |
| 1115 | pub const SOUND_CUES_OFF_BY_DEFAULT: [&str; 1] = ["sent"]; | |
| 1116 | pub const DEFAULT_SOUND_SET: &str = "soft"; | |
| 1117 | pub 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)] | |
| 1122 | pub 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 | ||
| 1137 | impl 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)] | |
| 1154 | pub 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")] | |
| 1171 | pub 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")] | |
| 1180 | pub struct SetSoundSettingsArgs { | |
| 1181 | pub user: User, | |
| 1182 | #[serde(default)] | |
| 1183 | pub change: SoundSettingsChange, | |
| 1184 | } | |
| 1185 | ||
| Fixes from the page sweep | 1186 | /// `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)] | |
| 1191 | pub 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")] | |
| 1203 | pub 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)] | |
| 1218 | pub 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 sweep | 1230 | |
| 1231 | /// How long an invite works, unless identity's `INVITE_TTL_DAYS` var says | |
| 1232 | /// otherwise. | |
| 1233 | pub 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 sweep | 1237 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 1238 | #[serde(rename_all = "snake_case")] | |
| 1239 | pub 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 sweep | 1249 | 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 sweep | 1252 | 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")] | |
| 1259 | pub 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")] | |
| 1269 | pub 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 sweep | 1303 | /// 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)] | |
| 1315 | pub 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 confirm | 1325 | /// 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 sweep | 1329 | } |
| 1330 | ||
| 1331 | /// Someone shown on an invite. | |
| 1332 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 1333 | pub 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)] | |
| 1342 | pub 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")] | |
| 1352 | pub 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 confirm | 1375 | /// 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 sweep | 1381 | } |
| 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)] | |
| 1388 | pub 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 sweep | 1398 | #[derive(Debug, Serialize, Deserialize)] |
| 1399 | pub 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 sweep | 1404 | 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 sweep | 1417 | /// 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. |
| 1423 | pub 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")] | |
| 1430 | pub 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)] | |
| 1449 | pub 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)] | |
| 1462 | pub struct FindPeopleArgs { | |
| 1463 | pub query: String, | |
| 1464 | #[serde(default)] | |
| 1465 | pub limit: Option<u32>, | |
| 1466 | } | |
| 1467 | ||
| Fixes from the page sweep | 1468 | /// `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 sweep | 1478 | #[derive(Debug, Serialize, Deserialize)] |
| 1479 | pub 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 sweep | 1488 | pub 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)] | |
| 1495 | pub 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)] | |
| 1503 | pub 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 sweep | 1516 | |
| 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")] | |
| 1520 | pub 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 sweep | 1526 | } |
| 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 sweep | 1531 | #[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. | 1532 | pub struct AdminSuspendArgs { |
| Fixes from the page sweep | 1533 | 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 sweep | 1535 | #[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 sweep | 1537 | 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 sweep | 1542 | #[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. | 1543 | pub 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 sweep | 1548 | /// 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 sweep | 1553 | /// |
| 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)] | |
| 1557 | pub 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)] | |
| 1573 | pub struct SharedInviteSource { | |
| 1574 | pub id: String, | |
| 1575 | pub label: String, | |
| Fixes from the page sweep | 1576 | } |
| 1577 | ||
| 1578 | #[cfg(test)] | |
| 1579 | mod 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.