Skip to content

Commit

Merge membership: owners, org roles, GitHub's repo roles, privileges, 2FA

Conflicts: Op::ALL is 281 (the run-protection operations and the five member operations); identity keeps the job-token RPCs beside the audited remove_access_token. Fixed on the way, both found by CI's own commands on the merged tree: - DEFAULT_MEMBER_PRIVILEGES lives in contracts' access.ts, its only user, so access.ts has no value import without an extension: services that test with plain `node --test` (context, deployments, projects) could not load it. - The routing-savings tests price on fixed tiers. They read the live routing, so Haiku 5.5 on the fast tier (and plans starting there) broke their arithmetic and would have failed every pull request's CI.

syntaqxcommitted Parents4edb70b43c5894Browse files
128 files+576−850/128 viewed
+2−0
10071007 "g1t-secrets",
10081008 "getrandom 0.2.17",
10091009 "hex",
1010+ "hmac 0.12.1",
10101011 "pbkdf2 0.12.2",
10111012 "serde",
10121013 "serde_json",
1014+ "sha1 0.10.7",
10131015 "sha2 0.10.9",
10141016 "worker",
10151017 ]
+10−0
6161 &[Op::GetWorkspace, Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
6262 ),
6363 (
64+ "Members",
65+ "A workspace's members and owners: who belongs to it, their roles (owner or member, with billing manager and security manager on top), handing it to another member, and leaving it.",
66+ &[Op::ListMembers, Op::UpdateMember, Op::RemoveMember, Op::TransferOwnership, Op::LeaveWorkspace],
67+ ),
68+ (
6469 "Invites",
6570 "While g1t is invite-only, every new account needs an invite. Your invites, and inviting people into a workspace by email.",
6671 &[
481486 Op::CreateWorkspace => "Create a workspace",
482487 Op::DeleteWorkspace => "Delete a workspace",
483488 Op::UpdateWorkspace => "Update a workspace",
489+ Op::ListMembers => "List a workspace's members",
490+ Op::UpdateMember => "Change a member's role",
491+ Op::RemoveMember => "Remove a member",
492+ Op::TransferOwnership => "Transfer a workspace's ownership",
493+ Op::LeaveWorkspace => "Leave a workspace",
484494 Op::ListEmails => "List your email addresses",
485495 Op::AddEmail => "Add an email address",
486496 Op::RemoveEmail => "Remove an email address",
+251−21
101101 CreateWorkspace,
102102 DeleteWorkspace,
103103 UpdateWorkspace,
104+ ListMembers,
105+ UpdateMember,
106+ RemoveMember,
107+ TransferOwnership,
108+ LeaveWorkspace,
104109 ListEmails,
105110 AddEmail,
106111 RemoveEmail,
652657 }
653658
654659 impl Op {
655− pub const ALL: [Op; 276] = [
660+ pub const ALL: [Op; 281] = [
656661 Op::Whoami,
657662 Op::GetWorkspace,
658663 Op::CreateWorkspace,
659664 Op::DeleteWorkspace,
660665 Op::UpdateWorkspace,
666+ Op::ListMembers,
667+ Op::UpdateMember,
668+ Op::RemoveMember,
669+ Op::TransferOwnership,
670+ Op::LeaveWorkspace,
661671 Op::ListEmails,
662672 Op::AddEmail,
663673 Op::RemoveEmail,
943953 Op::CreateWorkspace => "create_workspace",
944954 Op::DeleteWorkspace => "delete_workspace",
945955 Op::UpdateWorkspace => "update_workspace",
956+ Op::ListMembers => "list_members",
957+ Op::UpdateMember => "update_member",
958+ Op::RemoveMember => "remove_member",
959+ Op::TransferOwnership => "transfer_ownership",
960+ Op::LeaveWorkspace => "leave_workspace",
946961 Op::ListEmails => "list_emails",
947962 Op::AddEmail => "add_email",
948963 Op::RemoveEmail => "remove_email",
11671182 "Delete a workspace and everything in it. Owners only, signed in as a person, and confirm must be the workspace's slug. Billing must be able to settle it: no unpaid invoice, no prepaid credit left, and no usage this month still being metered; what it owes is charged to its card at once and its plan ends. Its repositories, projects and apps go with it at once, nobody can reach it, and its access tokens stop working. It is kept for 30 days, when g1t's support can restore it as it was; then it is purged, with its webhooks, integrations and workspace secrets. Its statements, invoices and audit log are kept. The slug is never given to another workspace; the person whose username it is may create it again once it is purged. Some workspaces, such as Flagon's, can never be deleted."
11681183 }
11691184 Op::GetWorkspace => {
1170− "One workspace you belong to: its name, description and member count, what every member gets on each of its repositories (base_permission), and who may create its teams (team_creation: members or owners). Members only."
1185+ "One workspace you belong to: its name, description and member count, what every member gets on each of its repositories (base_permission), who may create its teams (team_creation: members or owners), its member privileges (members_can_create_public_repositories, members_can_create_private_repositories, members_can_change_repo_visibility, members_can_delete_repositories, members_can_invite_outside_collaborators), and whether it requires two-factor authentication (two_factor_requirement_enabled). Members only."
11711186 }
11721187 Op::UpdateWorkspace => {
1173− "Change a workspace's display name and description, what every member gets on each of its repositories (base_permission: none, read, write or admin), and who may create its teams (team_creation: members or owners). Only the fields given are changed; give at least one. An empty name falls back to the slug, which this never changes (that is a rename, on Settings); an empty description clears it. Owners only, signed in as a person. Returns the workspace as it is now."
1188+ "Change a workspace's display name and description, what every member gets on each of its repositories (base_permission: none, read, write or admin), who may create its teams (team_creation: members or owners), its member privileges, and whether it requires two-factor authentication. The member privileges are: members_can_create_public_repositories and members_can_create_private_repositories (who may create each kind; owners always can), members_can_change_repo_visibility (members with the Admin role on a repository may make it public or private), members_can_delete_repositories (they may delete or transfer it) and members_can_invite_outside_collaborators (they may give a role to someone outside the workspace). two_factor_requirement_enabled true holds every member and outside collaborator without two-factor authentication out of the workspace until they turn it on; you need it on yourself first. Only the fields given are changed; give at least one. An empty name falls back to the slug, which this never changes (that is a rename, on Settings); an empty description clears it. Owners only, signed in as a person. Returns the workspace as it is now."
1189+ }
1190+ Op::ListMembers => {
1191+ "A workspace's members, owners first, then by username. Each has their `username`, `name`, `avatar`, `role` (`owner` or `member`), the roles they hold besides it (`org_roles`: `billing_manager`, `security_manager`), and, when an owner asks, whether they have two-factor authentication on (`two_factor`; null for anyone else). Members only."
1192+ }
1193+ Op::UpdateMember => {
1194+ "Change a member's role in a workspace: `role` (`owner` or `member`) and the roles they hold besides it (`org_roles`, a list of `billing_manager` and `security_manager`, which replaces the one they have). Only the fields given are changed. A billing manager manages the workspace's billing as an owner does, and gets nothing on repositories from it; a security manager reads every repository and sees and manages its security alerts and security settings. Refused with `409` when it would leave the workspace without an owner. Owners only, signed in as a person. Returns the member."
1195+ }
1196+ Op::RemoveMember => {
1197+ "Remove someone from a workspace. Their roles on its repositories and their place in its teams go too; to keep them on a repository, add them back to it as an outside collaborator. Removing yourself is leaving (leave_workspace). Refused with `409` for the last owner. Owners only, signed in as a person."
1198+ }
1199+ Op::TransferOwnership => {
1200+ "Hand a workspace to another of its members: they become an owner and you a member, in one step. A workspace can have several owners; to add one without stepping down, use update_member with role owner. Owners only, signed in as a person."
1201+ }
1202+ Op::LeaveWorkspace => {
1203+ "Leave a workspace you belong to. Your roles on its repositories and your place in its teams go too. The last owner cannot leave (`409`): make another member an owner first, or delete the workspace. People only."
11741204 }
11751205 Op::ListRepos => "Repositories you can see, optionally filtered by a search query.",
11761206 Op::GetRepo => "One repository's details.",
11771207 Op::UpdateRepo => {
1178− "Change a repository's description, website, topics and default branch, whether its default branch is protected, and whether it is private. Only the fields given are changed. Its description, website and topics, and protecting its default branch, need the Maintain role or higher; making it public or private and changing its default branch need the Admin role, and a free workspace takes a private repository only while its private storage has room. A protected branch refuses pushes and changes only by merging a pull request. A new default branch must already exist; open pull requests then merge into it."
1208+ "Change a repository's description, website, topics and default branch, whether its default branch is protected, and whether it is private. Only the fields given are changed. Its description, website and topics need the Maintain role or higher; protecting its default branch, making it public or private and changing its default branch need the Admin role (and making it public or private, the workspace's member privileges to allow it, unless you are an owner), and a free workspace takes a private repository only while its private storage has room. A protected branch refuses pushes and changes only by merging a pull request. A new default branch must already exist; open pull requests then merge into it."
11791209 }
11801210 Op::RenameRepo => {
11811211 "Give a repository a new name in its workspace. Needs the Admin role. Everything stays with it: git data, issues, pull requests, workflow runs, deployments, secrets and webhooks. Its old address keeps working: web pages, git remotes and API calls redirect to the new one until a repository is made at the old address. The new name must be free in the workspace, including names held by recently deleted repositories."
11901220 "Unarchive a repository: make it writable again. Needs the Admin role. Pushes, merges, issues, pull requests, agents and workflows work again; nothing that was refused while it was archived runs by itself."
11911221 }
11921222 Op::SetRepoVisibility => {
1193− "Make a repository public or private. Needs the Admin role, and confirm must be its full name, owner/name. Making it public shows it, its code, issues and pull requests to everyone and adds it to search for everyone. Making it private hides it from everyone without a role on it; a free workspace takes it only while its private storage has room. Nothing else about it changes."
1223+ "Make a repository public or private. Needs the Admin role, and confirm must be its full name, owner/name. Unless you are an owner of its workspace, the workspace's member privileges must let repository admins change visibility (members_can_change_repo_visibility) and let members create a repository of that kind. Making it public shows it, its code, issues and pull requests to everyone and adds it to search for everyone. Making it private hides it from everyone without a role on it; a free workspace takes it only while its private storage has room. Nothing else about it changes."
11941224 }
11951225 Op::DeleteRepo => {
11961226 "Delete a repository. Owners only, and confirm must be its full name, owner/name. It disappears at once: git refuses it, agents and workflows stop, its deployments are taken down, and search drops it. For 30 days an owner can restore it with restore_repo, as it was; then it is purged, its git data with it. Its name stays taken until it is purged. list_deleted_repos shows what can be restored."
12111241 "How a repository handles pull requests: how g1t's agents are reviewed, revised and merged, and its default branch's protection as the rules of its rulesets stack there: the checks that must pass (required_checks), the approvals a merge needs, whether its code owners must approve (`require_code_owner_review`), whether required checks can be bypassed, whether a pull request must be up to date, and the merge queue. The same rules hold for a person's pull request and an agent's. list_repo_rulesets and get_branch_rules show every rule."
12121242 }
12131243 Op::UpdateRepoSettings => {
1214− "Change how a repository handles pull requests. Only the fields given are changed; required_checks replaces the whole list. The branch protection fields (required_checks, require_up_to_date, required_approvals, count_agent_approvals, allow_ignoring_checks, merge_queue, require_code_owner_review) are written to the repository's \"Default branch protection\" ruleset, made when it has none; rules only rulesets have stay as they are. A required check is named as list_check_names gives it: a workflow's name, such as CI, or another status's context, such as g1t / deploy. Needs the Maintain role or higher."
1244+ "Change how a repository handles pull requests. Only the fields given are changed; required_checks replaces the whole list. The branch protection fields (required_checks, require_up_to_date, required_approvals, count_agent_approvals, allow_ignoring_checks, merge_queue, require_code_owner_review) are written to the repository's \"Default branch protection\" ruleset, made when it has none; rules only rulesets have stay as they are. A required check is named as list_check_names gives it: a workflow's name, such as CI, or another status's context, such as g1t / deploy. Needs the Maintain role or higher, and the Admin role to change a branch protection field."
12151245 }
12161246 Op::ListCheckNames => {
12171247 "The check names reported on a repository's commits in the last 30 days, most recent first, with the events each was reported for: the names update_repo_settings takes in required_checks. A workflow's runs report a check named after the workflow; a check required on the default branch must be reported on a pull request's head (pull_request events) and, with the merge queue on, on its queued state (merge_group events)."
12811311 "A repository's labels, by name: each one's color (six hex digits), description, and how many issues and pull requests carry it. A new repository starts with bug, documentation, duplicate, enhancement, good first issue, help wanted, invalid, question, wontfix, dependencies and security."
12821312 }
12831313 Op::CreateLabel => {
1284− "Create a label, named by label. Names are lowercase and unique in a repository, at most 50 characters; color is six hex digits (one is chosen from the name when left out), description at most 100 characters. Needs the Triage role or higher."
1314+ "Create a label, named by label. Names are lowercase and unique in a repository, at most 50 characters; color is six hex digits (one is chosen from the name when left out), description at most 100 characters. Needs the Write role or higher; applying labels and milestones needs Triage."
12851315 }
12861316 Op::UpdateLabel => {
1287− "Change a label's name, color or description; only the fields given change. Renaming it renames it on every issue and pull request that carries it. Needs the Triage role or higher."
1317+ "Change a label's name, color or description; only the fields given change. Renaming it renames it on every issue and pull request that carries it. Needs the Write role or higher; applying labels and milestones needs Triage."
12881318 }
12891319 Op::DeleteLabel => {
1290− "Delete a label. It is taken off every issue and pull request that carries it, without events for each. Needs the Triage role or higher."
1320+ "Delete a label. It is taken off every issue and pull request that carries it, without events for each. Needs the Write role or higher; applying labels and milestones needs Triage."
12911321 }
12921322 Op::AddDefaultLabels => {
1293− "Add the default labels a repository does not have yet: bug, documentation, duplicate, enhancement, good first issue, help wanted, invalid, question, wontfix, dependencies and security. Labels it has already are left as they are. Returns every label it has now. Needs the Triage role or higher."
1323+ "Add the default labels a repository does not have yet: bug, documentation, duplicate, enhancement, good first issue, help wanted, invalid, question, wontfix, dependencies and security. Labels it has already are left as they are. Returns every label it has now. Needs the Write role or higher; applying labels and milestones needs Triage."
12941324 }
12951325 Op::ListIssueLabels => {
12961326 "The labels an issue or a pull request carries, with their colors and descriptions. Issues and pull requests share numbers."
12971327 }
12981328 Op::AddIssueLabels => {
1299− "Add labels to an issue or a pull request, keeping the ones it has. A name the repository does not have yet is created when you have the Triage role or higher; without it, you may use the repository's labels on what you opened. Each label added is an issue.labeled or pull.labeled event. Returns its labels now, at most 20."
1329+ "Add labels to an issue or a pull request, keeping the ones it has. A name the repository does not have yet is created when you have the Write role or higher; without it, you may use the repository's labels on what you opened. Each label added is an issue.labeled or pull.labeled event. Returns its labels now, at most 20."
13001330 }
13011331 Op::SetIssueLabels => {
13021332 "Replace the labels of an issue or a pull request with these; an empty list takes them all off. The same rules as add_issue_labels. Returns its labels now."
13091339 }
13101340 Op::GetMilestone => "A milestone, with every issue and pull request in it, newest first.",
13111341 Op::CreateMilestone => {
1312− "Create a milestone: a title, unique in the repository, at most 100 characters; a description in Markdown; and a due_on day (YYYY-MM-DD). Milestones are numbered from 1 in each repository, apart from issues. Needs the Triage role or higher."
1342+ "Create a milestone: a title, unique in the repository, at most 100 characters; a description in Markdown; and a due_on day (YYYY-MM-DD). Milestones are numbered from 1 in each repository, apart from issues. Needs the Write role or higher; applying labels and milestones needs Triage."
13131343 }
13141344 Op::UpdateMilestone => {
1315− "Change a milestone's title, description, due date or state (open or closed); only the fields given change, and due_on \"\" clears its due date. Needs the Triage role or higher."
1345+ "Change a milestone's title, description, due date or state (open or closed); only the fields given change, and due_on \"\" clears its due date. Needs the Write role or higher; applying labels and milestones needs Triage."
13161346 }
13171347 Op::DeleteMilestone => {
1318− "Delete a milestone. The issues and pull requests in it are in no milestone afterwards. Needs the Triage role or higher."
1348+ "Delete a milestone. The issues and pull requests in it are in no milestone afterwards. Needs the Write role or higher; applying labels and milestones needs Triage."
13191349 }
13201350 Op::AddComment => {
13211351 "Comment on an issue or a pull request. On a pull request, give path and line to comment on one line of the change."
14801510 "Decline an invitation to a repository sent to you. Whoever sent it can invite you again. People only."
14811511 }
14821512 Op::SetBasePermission => {
1483− "Set what every member of a workspace gets on each of its repositories: none, read, write (the default) or admin. Owners always have Admin, and a role given on a repository directly still counts where it is higher. With none, members see only the private repositories they are given a role on. Owners only, signed in as a person."
1513+ "Set what every member of a workspace gets on each of its repositories: none, read (what a new workspace starts with), write or admin. Owners always have Admin, and a role given on a repository directly still counts where it is higher. With none, members see only the private repositories they are given a role on. Owners only, signed in as a person."
14841514 }
14851515 Op::ListOutsideCollaborators => {
14861516 "The people with a role on some of a workspace's repositories who are not its members, each with the repositories they can reach and their role on each. Owners only."
14891519 "A repository's security alerts: secrets found in what was pushed or in its history (`kind` `secret`), and dependencies with a known vulnerability (`kind` `dependency`), secrets first. Each has a `state`: `open`, `dismissed` (someone said why it can stay) or `fixed` (a secret revoked, a dependency no longer vulnerable). Filter with `state` and `kind`; both are left out for all. A secret is never returned, only a `preview`. Needs the Write role on the repository; anyone else is told it does not exist, whether or not the repository is public."
14901520 }
14911521 Op::DismissSecurityAlert => {
1492− "Dismiss an alert with a reason and an optional comment. A secret takes false_positive, used_in_tests, revoked or wont_fix; a dependency takes fix_started, no_bandwidth, tolerable_risk, inaccurate or not_used. A dismissed secret is let through push protection from then on, unless the reason is `revoked`, which marks it fixed, so dismissing a secret needs the Admin role on the repository; a dependency needs Write. Returns the alert as it is now. Reopen it with reopen_security_alert."
1522+ "Dismiss an alert with a reason and an optional comment. A secret takes false_positive, used_in_tests, revoked or wont_fix; a dependency takes fix_started, no_bandwidth, tolerable_risk, inaccurate or not_used. A dismissed secret is let through push protection from then on, unless the reason is `revoked`, which marks it fixed. Dismissing either needs the Write role on the repository, or a security manager of its workspace. Returns the alert as it is now. Reopen it with reopen_security_alert."
14931523 }
14941524 Op::ReopenSecurityAlert => {
14951525 "Open a dismissed alert again. A reopened secret stops pushes that carry it again. The same roles as dismissing: Admin for a secret, Write for a dependency. Returns the alert as it is now."
17641794 "enum": g1t_contracts::teams::TeamCreation::ALL.map(|setting| setting.as_str()),
17651795 "description": "Who may create the workspace's teams: members (any member, the default) or owners (owners only).",
17661796 },
1797+ "members_can_create_public_repositories": {
1798+ "type": "boolean",
1799+ "description": "Members may create public repositories. Owners always can. On by default.",
1800+ },
1801+ "members_can_create_private_repositories": {
1802+ "type": "boolean",
1803+ "description": "Members may create private repositories. Owners always can. On by default.",
1804+ },
1805+ "members_can_change_repo_visibility": {
1806+ "type": "boolean",
1807+ "description": "Members with the Admin role on a repository may make it public or private. On by default; off, only owners can.",
1808+ },
1809+ "members_can_delete_repositories": {
1810+ "type": "boolean",
1811+ "description": "Members with the Admin role on a repository may delete or transfer it. Off by default: only owners can.",
1812+ },
1813+ "members_can_invite_outside_collaborators": {
1814+ "type": "boolean",
1815+ "description": "Members with the Admin role on a repository may give a role on it to someone outside the workspace. On by default; off, only owners can.",
1816+ },
1817+ "two_factor_requirement_enabled": {
1818+ "type": "boolean",
1819+ "description": "Require two-factor authentication of every member and outside collaborator. Those without it keep their place but cannot use the workspace until they turn it on. You need it on yourself first.",
1820+ },
17671821 }),
17681822 &["workspace"],
17691823 ),
1824+ Op::ListMembers | Op::LeaveWorkspace => object(json!({ "workspace": workspace_schema() }), &["workspace"]),
1825+ Op::UpdateMember => object(
1826+ json!({
1827+ "workspace": workspace_schema(),
1828+ "username": { "type": "string", "description": "The member's username." },
1829+ "role": {
1830+ "type": "string",
1831+ "enum": ["owner", "member"],
1832+ "description": "owner or member.",
1833+ },
1834+ "org_roles": {
1835+ "type": "array",
1836+ "items": { "type": "string", "enum": g1t_contracts::OrgRole::ALL.map(|role| role.as_str()) },
1837+ "description": "The roles they hold besides owner or member: billing_manager, security_manager. Replaces the list; [] takes them all away.",
1838+ },
1839+ }),
1840+ &["workspace", "username"],
1841+ ),
1842+ Op::RemoveMember | Op::TransferOwnership => object(
1843+ json!({
1844+ "workspace": workspace_schema(),
1845+ "username": { "type": "string", "description": "The member's username." },
1846+ }),
1847+ &["workspace", "username"],
1848+ ),
17701849 Op::TransferRepo => object(
17711850 json!({
17721851 "repo": repo_schema(),
18041883 "labels": {
18051884 "type": "array",
18061885 "items": { "type": "string" },
1807− "description": "Label names, e.g. [\"bug\", \"help wanted\"]. Names the repository does not have yet are created for someone with the Triage role.",
1886+ "description": "Label names, e.g. [\"bug\", \"help wanted\"]. Names the repository does not have yet are created for someone with the Write role.",
18081887 },
18091888 })),
18101889 &["repo", "number", "labels"],
21052184 "labels": {
21062185 "type": "array",
21072186 "items": { "type": "string" },
2108− "description": "What kind of issue this is, e.g. \"bug\" or \"enhancement\": the repository's labels, as list_labels gives them. A name it does not have yet is created for someone with the Triage role.",
2187+ "description": "What kind of issue this is, e.g. \"bug\" or \"enhancement\": the repository's labels, as list_labels gives them. A name it does not have yet is created for someone with the Write role.",
21092188 },
21102189 "checks": {
21112190 "type": "array",
21242203 "labels": {
21252204 "type": "array",
21262205 "items": { "type": "string" },
2127− "description": "Replaces the whole set. Names the repository does not have yet are created for someone with the Triage role.",
2206+ "description": "Replaces the whole set. Names the repository does not have yet are created for someone with the Write role.",
21282207 },
21292208 "milestone": {
21302209 "type": ["integer", "null"],
30903169 | Op::CreateWorkspace
30913170 | Op::DeleteWorkspace
30923171 | Op::UpdateWorkspace
3172+ | Op::ListMembers
3173+ | Op::UpdateMember
3174+ | Op::RemoveMember
3175+ | Op::TransferOwnership
3176+ | Op::LeaveWorkspace
30933177 | Op::ListEmails
30943178 | Op::AddEmail
30953179 | Op::RemoveEmail
34683552 None => return failed(FailureCode::Invalid, "team_creation is members or owners."),
34693553 },
34703554 };
3555+ let privileges = match g1t_contracts::members::MemberPrivilegesPatch::from_json(input) {
3556+ Ok(patch) => patch,
3557+ Err(message) => return failed(FailureCode::Invalid, &message),
3558+ };
3559+ let two_factor = match input.get("two_factor_requirement_enabled").filter(|value| !value.is_null()) {
3560+ None => None,
3561+ Some(Value::Bool(required)) => Some(*required),
3562+ Some(_) => return failed(FailureCode::Invalid, "two_factor_requirement_enabled is true or false."),
3563+ };
34713564 let (name, description) = (optional_text(input, "name"), optional_text(input, "description"));
3472− if base.is_none() && creation.is_none() && name.is_none() && description.is_none() {
3473− return failed(FailureCode::Invalid, "Give name, description, base_permission or team_creation to change.");
3565+ if base.is_none()
3566+ && creation.is_none()
3567+ && name.is_none()
3568+ && description.is_none()
3569+ && privileges.is_empty()
3570+ && two_factor.is_none()
3571+ {
3572+ return failed(
3573+ FailureCode::Invalid,
3574+ "Give name, description, base_permission, team_creation, a member privilege or two_factor_requirement_enabled to change.",
3575+ );
34743576 }
34753577 let found = || async {
34763578 g1t_kit::call::<_, Option<Workspace>>(identity, "get_workspace", &json!({ "slug": workspace() })).await
35273629 return Ok(Outcome::Fail(failure));
35283630 }
35293631 }
3632+ if !privileges.is_empty() {
3633+ let set: Outcome<g1t_contracts::MemberPrivileges> = call(
3634+ identity,
3635+ "set_member_privileges",
3636+ &g1t_contracts::members::SetMemberPrivilegesArgs {
3637+ actor: actor(),
3638+ slug: workspace(),
3639+ privileges,
3640+ surface: Some(services.audit.surface),
3641+ },
3642+ )
3643+ .await?;
3644+ if let Outcome::Fail(failure) = set {
3645+ return Ok(Outcome::Fail(failure));
3646+ }
3647+ }
3648+ if let Some(required) = two_factor {
3649+ let set: Outcome<bool> = call(
3650+ identity,
3651+ "set_two_factor_requirement",
3652+ &g1t_contracts::members::SetTwoFactorRequirementArgs {
3653+ actor: actor(),
3654+ slug: workspace(),
3655+ required,
3656+ surface: Some(services.audit.surface),
3657+ },
3658+ )
3659+ .await?;
3660+ if let Outcome::Fail(failure) = set {
3661+ return Ok(Outcome::Fail(failure));
3662+ }
3663+ }
35303664 match found().await? {
35313665 Some(workspace) => ok(&workspace),
35323666 None => failed(FailureCode::NotFound, "Workspace not found."),
35333667 }
35343668 }
3669+ Op::ListMembers => pass(identity, "list_members", &json!({ "slug": workspace(), "viewer": viewer })).await,
3670+ Op::UpdateMember => {
3671+ let role = match input.get("role").filter(|value| !value.is_null()) {
3672+ None => None,
3673+ Some(value) => match value.as_str().map(|text| text.trim().to_ascii_lowercase()).as_deref() {
3674+ Some("owner") | Some("admin") => Some(g1t_contracts::Role::Owner),
3675+ Some("member") => Some(g1t_contracts::Role::Member),
3676+ _ => return failed(FailureCode::Invalid, "role is owner or member."),
3677+ },
3678+ };
3679+ let org_roles = match input.get("org_roles").filter(|value| !value.is_null()) {
3680+ None => None,
3681+ Some(Value::Array(items)) => {
3682+ let mut roles = Vec::new();
3683+ for item in items {
3684+ match item.as_str().and_then(g1t_contracts::OrgRole::parse) {
3685+ Some(role) => roles.push(role),
3686+ None => return failed(FailureCode::Invalid, "org_roles lists billing_manager and security_manager."),
3687+ }
3688+ }
3689+ Some(roles)
3690+ }
3691+ Some(_) => return failed(FailureCode::Invalid, "org_roles is a list: billing_manager, security_manager."),
3692+ };
3693+ pass(
3694+ identity,
3695+ "update_member",
3696+ &g1t_contracts::members::UpdateMemberArgs {
3697+ actor: actor(),
3698+ slug: workspace(),
3699+ username: text(input, "username"),
3700+ role,
3701+ org_roles,
3702+ surface: Some(services.audit.surface),
3703+ },
3704+ )
3705+ .await
3706+ }
3707+ Op::RemoveMember => {
3708+ pass(
3709+ identity,
3710+ "remove_member",
3711+ &json!({
3712+ "actor": actor(),
3713+ "slug": workspace(),
3714+ "username": text(input, "username"),
3715+ "surface": services.audit.surface,
3716+ }),
3717+ )
3718+ .await
3719+ }
3720+ Op::TransferOwnership => {
3721+ pass(
3722+ identity,
3723+ "transfer_ownership",
3724+ &g1t_contracts::members::TransferOwnershipArgs {
3725+ actor: actor(),
3726+ slug: workspace(),
3727+ username: text(input, "username"),
3728+ surface: Some(services.audit.surface),
3729+ },
3730+ )
3731+ .await
3732+ }
3733+ Op::LeaveWorkspace => {
3734+ pass(
3735+ identity,
3736+ "leave_workspace",
3737+ &g1t_contracts::members::LeaveWorkspaceArgs {
3738+ user: actor(),
3739+ slug: workspace(),
3740+ surface: Some(services.audit.surface),
3741+ },
3742+ )
3743+ .await
3744+ }
35353745 Op::TransferRepo => {
35363746 pass(
35373747 repos,
53835593 Op::ListOutsideCollaborators,
53845594 ];
53855595
5596+ const MEMBERS: [Op; 5] = [Op::ListMembers, Op::UpdateMember, Op::RemoveMember, Op::TransferOwnership, Op::LeaveWorkspace];
5597+
5598+ /// Who belongs to a workspace, and who owns it, is people's business:
5599+ /// no run lists these, and agents are refused them whatever a scope says.
5600+ #[test]
5601+ fn agents_never_manage_members() {
5602+ use g1t_contracts::credentials::{CredentialUse, NEVER, RunCredentialKind, operations_for};
5603+ for op in MEMBERS {
5604+ assert!(NEVER.contains(&op.name()), "{} is not in NEVER", op.name());
5605+ assert!(!op.needs_repo(), "{}", op.name());
5606+ assert!(op.needs_user(), "{}", op.name());
5607+ for kind in RunCredentialKind::ALL {
5608+ for usage in [CredentialUse::Runner, CredentialUse::Tools] {
5609+ assert!(!operations_for(kind, usage).contains(&op.name()));
5610+ }
5611+ }
5612+ }
5613+ assert_eq!(Op::UpdateMember.input()["properties"]["org_roles"]["items"]["enum"], json!(["billing_manager", "security_manager"]));
5614+ }
5615+
53865616 /// Who has access is for people: no run's scope lists these, and the
53875617 /// ones that change or reveal access are refused whatever a scope says.
53885618 #[test]
+111−11
6666 "description": null,
6767 "created_at": "2026-10-04T16:02:51.337Z",
6868 "member_count": 1,
69− "base_permission": "write",
70− "team_creation": "members"
69+ "base_permission": "read",
70+ "team_creation": "members",
71+ "members_can_create_public_repositories": true,
72+ "members_can_create_private_repositories": true,
73+ "members_can_change_repo_visibility": true,
74+ "members_can_delete_repositories": false,
75+ "members_can_invite_outside_collaborators": true,
76+ "two_factor_requirement_enabled": false
7177 },
72− "notes": "`base_permission` is what every member gets on each of its repositories: `write` until an owner changes it with `PATCH /workspaces/{workspace}` or `PUT /workspaces/{workspace}/base_permission`. See [Access and roles](/guides/access-and-roles/). `team_creation` is who may create its teams: `members` (any member) until an owner sets `owners` with `PATCH /workspaces/{workspace}`. A new workspace is free, and each person owns at most one free workspace: while you own one, this answers `402` with `payment_required` and says which, until it is on the plan or deleted. See [One free workspace per person](/guides/usage-and-billing/#one-free-workspace-per-person)."
78+ "notes": "`base_permission` is what every member gets on each of its repositories: `read` for a new workspace until an owner changes it with `PATCH /workspaces/{workspace}` or `PUT /workspaces/{workspace}/base_permission`. See [Access and roles](/guides/access-and-roles/). `team_creation` is who may create its teams: `members` (any member) until an owner sets `owners` with `PATCH /workspaces/{workspace}`. A new workspace is free, and each person owns at most one free workspace: while you own one, this answers `402` with `payment_required` and says which, until it is on the plan or deleted. See [One free workspace per person](/guides/usage-and-billing/#one-free-workspace-per-person)."
7379 },
7480 "get_workspace": {
7581 "params": {
8389 "created_at": "2026-10-04T16:02:51.337Z",
8490 "member_count": 3,
8591 "base_permission": "write",
86− "team_creation": "members"
92+ "team_creation": "members",
93+ "members_can_create_public_repositories": true,
94+ "members_can_create_private_repositories": true,
95+ "members_can_change_repo_visibility": true,
96+ "members_can_delete_repositories": false,
97+ "members_can_invite_outside_collaborators": true,
98+ "two_factor_requirement_enabled": false
8799 },
88− "notes": "`team_creation` is who may create its teams: `members` (any member, the default) or `owners`. Change it, and the rest, with [`PATCH /workspaces/{workspace}`](/reference/api/workspaces/update-workspace/). `404` for anyone who is not a member. See [Workspaces](/guides/workspaces/)."
100+ "notes": "`team_creation` is who may create its teams: `members` (any member, the default) or `owners`. The `members_can_…` fields are its [member privileges](/guides/workspaces/#member-privileges), and `two_factor_requirement_enabled` whether it [requires two-factor authentication](/guides/authentication/#require-two-factor-authentication). Change it, and the rest, with [`PATCH /workspaces/{workspace}`](/reference/api/workspaces/update-workspace/). `404` for anyone who is not a member. See [Workspaces](/guides/workspaces/)."
89101 },
90102 "update_workspace": {
91103 "params": {
94106 "request": {
95107 "name": "Acme Labs",
96108 "description": "Rockets, and the software that flies them.",
97− "team_creation": "owners"
109+ "team_creation": "owners",
110+ "members_can_delete_repositories": true
98111 },
99112 "response": {
100113 "id": "wsp_01m43teqa9em6bje0bhvdj4jkb",
104117 "created_at": "2026-10-04T16:02:51.337Z",
105118 "member_count": 3,
106119 "base_permission": "write",
107− "team_creation": "owners"
120+ "team_creation": "owners",
121+ "members_can_create_public_repositories": true,
122+ "members_can_create_private_repositories": true,
123+ "members_can_change_repo_visibility": true,
124+ "members_can_delete_repositories": true,
125+ "members_can_invite_outside_collaborators": true,
126+ "two_factor_requirement_enabled": false
127+ },
128+ "notes": "Only the fields given change. `name` is the display name; the slug, the first part of the workspace's addresses, stays as it is. `base_permission` needs the `access:admin` scope as well as `workspace:admin`; [`set_base_permission`](/reference/api/access/set-base-permission/) sets it alone. `team_creation` is `members` (any member may create a team, the default) or `owners`; teams already made stay. The `members_can_…` fields are the workspace's member privileges: who may create public and private repositories, and whether members with the Admin role on a repository may change its visibility, delete or transfer it, and invite outside collaborators. `two_factor_requirement_enabled` holds everyone without two-factor authentication out of the workspace until they turn it on, and needs it on your own account first. Refused with `403` for anyone but an owner signed in as a person. Recorded in the [audit log](/guides/audit-log/). See [Workspaces](/guides/workspaces/)."
129+ },
130+ "list_members": {
131+ "params": {
132+ "workspace": "acme-labs"
133+ },
134+ "response": [
135+ {
136+ "username": "ada",
137+ "role": "owner",
138+ "org_roles": [],
139+ "two_factor": true,
140+ "name": "Ada Lovelace",
141+ "avatar": null
142+ },
143+ {
144+ "username": "grace",
145+ "role": "member",
146+ "org_roles": [
147+ "security_manager"
148+ ],
149+ "two_factor": false,
150+ "name": "Grace Hopper",
151+ "avatar": null
152+ }
153+ ],
154+ "notes": "Owners first, then by username. `two_factor` is shown to owners only, and is `null` for anyone else. `403` for anyone who is not a member. See [Workspaces](/guides/workspaces/#members-and-owners)."
155+ },
156+ "update_member": {
157+ "params": {
158+ "workspace": "acme-labs",
159+ "username": "grace"
160+ },
161+ "request": {
162+ "role": "member",
163+ "org_roles": [
164+ "security_manager"
165+ ]
108166 },
109− "notes": "Only the fields given change. `name` is the display name; the slug, the first part of the workspace's addresses, stays as it is. `base_permission` needs the `access:admin` scope as well as `workspace:admin`; [`set_base_permission`](/reference/api/access/set-base-permission/) sets it alone. `team_creation` is `members` (any member may create a team, the default) or `owners`; teams already made stay. Refused with `403` for anyone but an owner signed in as a person. Recorded in the [audit log](/guides/audit-log/). See [Workspaces](/guides/workspaces/)."
167+ "response": {
168+ "username": "grace",
169+ "role": "member",
170+ "org_roles": [
171+ "security_manager"
172+ ],
173+ "two_factor": null,
174+ "name": "Grace Hopper",
175+ "avatar": null
176+ },
177+ "notes": "Only the fields given change; `org_roles` replaces the list. `409` when it would leave the workspace without an owner. Refused with `403` for anyone but an owner signed in as a person. Recorded in the [audit log](/guides/audit-log/) as `member.role_changed`, `member.org_role_added` or `member.org_role_removed`. See [Workspaces](/guides/workspaces/#members-and-owners)."
178+ },
179+ "remove_member": {
180+ "params": {
181+ "workspace": "acme-labs",
182+ "username": "grace"
183+ },
184+ "response": true,
185+ "notes": "Their roles on the workspace's repositories and their place in its teams go with them. Your own username is leaving, as [`leave_workspace`](/reference/api/members/leave-workspace/). `409` for the last owner. Recorded as `member.removed`."
110186 },
187+ "transfer_ownership": {
188+ "params": {
189+ "workspace": "acme-labs"
190+ },
191+ "request": {
192+ "username": "grace"
193+ },
194+ "response": true,
195+ "notes": "`username` becomes an owner and you a member, in one step. Recorded as `workspace.ownership_transferred`."
196+ },
197+ "leave_workspace": {
198+ "params": {
199+ "workspace": "acme-labs"
200+ },
201+ "response": true,
202+ "notes": "`409` for the last owner: make another member an owner first, or delete the workspace. Recorded as `member.left`."
203+ },
111204 "delete_workspace": {
112205 "request": {
113206 "confirm": "acme-labs"
37213814 "request": {
37223815 "config": {
37233816 "base_url": "https://gpu.flagon.dev/v1",
3724− "gateway_models": ["ollama/*"]
3817+ "gateway_models": [
3818+ "ollama/*"
3819+ ]
37253820 },
37263821 "secret": "sk-office-gpu-key"
37273822 },
37343829 "config": {
37353830 "write_back": true,
37363831 "base_url": "https://gpu.flagon.dev/v1",
3737− "gateway_models": ["ollama/*"]
3832+ "gateway_models": [
3833+ "ollama/*"
3834+ ]
37383835 },
37393836 "secret_hint": "…-key",
37403837 "webhook_url": null,
37423839 "created_at": "2026-10-07T12:10:44.512Z",
37433840 "last_used_at": "2026-10-07T14:00:02.000Z",
37443841 "last_error": null,
3745− "models": ["llama3.3:70b", "qwen3-coder:30b"]
3842+ "models": [
3843+ "llama3.3:70b",
3844+ "qwen3-coder:30b"
3845+ ]
37463846 },
37473847 "notes": "The secret is write-only: it is kept encrypted and only its last four characters come back, as `secret_hint`. With `gateway_models` set to `ollama/*`, an AI Gateway request for `ollama/qwen3-coder:30b` reaches this endpoint as `qwen3-coder:30b`, on the workspace's own account. See [the AI Gateway guide](/guides/ai-gateway/#your-own-providers)."
37483848 },
+4−0
8181 return through::<access::RepoInvitation>(op, as_is);
8282 }
8383 Op::ListOutsideCollaborators => return through::<Vec<access::OutsideCollaborator>>(op, as_is),
84+ // Members, also `snake_case`.
85+ Op::ListMembers => return through::<Vec<g1t_contracts::identity::Member>>(op, as_is),
86+ Op::UpdateMember => return through::<g1t_contracts::identity::Member>(op, as_is),
87+ Op::RemoveMember | Op::TransferOwnership | Op::LeaveWorkspace => return through::<bool>(op, as_is),
8488 // Teams and code owners, also `snake_case`.
8589 Op::ListTeams | Op::ListChildTeams | Op::ListUserTeams => return through::<Vec<teams::Team>>(op, as_is),
8690 Op::GetTeam | Op::CreateTeam | Op::UpdateTeam | Op::SetTeamReviewAssignment => {
+6−0
113113 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
114114 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
115115 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
116+ // Members and owners: GitHub's organization members, by username.
117+ route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
118+ route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
119+ route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
120+ route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
121+ route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
116122 // A workspace's projects: what each is, where it runs, its links.
117123 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
118124 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
+3−3
9999 match self {
100100 RulesOp::ListRepoRulesets => "List a repository's rulesets: what may happen to its branches and tags, and what a pull request needs before it merges. With include_parents, also its workspace's rulesets that hold in it (level workspace). Each has its enforcement (active, evaluate: a dry run that records what it would have refused, or disabled), target (branch or tag), conditions (ref_name include and exclude patterns: fnmatch, ~DEFAULT_BRANCH, ~ALL), bypass_actors and rules. The one made from branch protection settings has source branch_protection.",
101101 RulesOp::GetRepoRuleset => "Get one of a repository's rulesets by id (rs_…), or one of its workspace's that holds in it.",
102− RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, confidence_threshold, cost_cap, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Maintain role. Returns the ruleset as saved, tidied.",
103− RulesOp::UpdateRepoRuleset => "Change a repository ruleset. Fields left out stay as they are; rules and bypass_actors, when given, replace the whole list. Takes the Maintain role.",
104− RulesOp::DeleteRepoRuleset => "Delete a repository ruleset. Its evaluations stay in the log. Takes the Maintain role.",
102+ RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, confidence_threshold, cost_cap, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Admin role. Returns the ruleset as saved, tidied.",
103+ RulesOp::UpdateRepoRuleset => "Change a repository ruleset. Fields left out stay as they are; rules and bypass_actors, when given, replace the whole list. Takes the Admin role.",
104+ RulesOp::DeleteRepoRuleset => "Delete a repository ruleset. Its evaluations stay in the log. Takes the Admin role.",
105105 RulesOp::GetBranchRules => "Every rule that holds for a branch (or a tag, with target tag) of a repository, from every ruleset that targets it, the repository's and its workspace's: each with its type, parameters and applies_to, and the ruleset_id, ruleset_name, level and enforcement it comes from. Active rules come first, then those of rulesets in evaluate. rulesets lists the rulesets with who may bypass each. A branch name with slashes is URL-encoded in the path.",
106106 RulesOp::ListRuleEvaluations => "List how a repository's rulesets judged pushes, merges and other changes to its branches and tags, newest first: the ruleset, the action (push, merge, create_ref, delete_ref, rename_ref or commit), the ref, the actor and whether they are a person, an agent or g1t, the verdict (pass, fail or bypass) and each rule broken with why. A fail of a ruleset in evaluate is what it would have refused. Filter by ruleset_id or verdict, or problems_only; page with before. insights counts the last 30 days by ruleset and by rule. Takes the Write role.",
107107 RulesOp::ListWorkspaceRulesets => "List a workspace's own rulesets. Each holds in the repositories its conditions.repository selects: names matching include (fnmatch, or ~ALL) and not exclude, of a visibility (any, public or private), and carrying one of topics when given. Members only.",
+3−3
174174 match self {
175175 SecurityOp::ListSecretAlerts => "List secret scanning alerts: secrets found in pushes (blocked) and in history (open), newest first, in a repository or (with workspace) across a workspace. Filter by state (open, dismissed, fixed), secret_type, validity (active, inactive, unknown, unsupported) and bypassed. The secret itself is never returned: a preview and a fingerprint-based id only.",
176176 SecurityOp::GetSecretAlert => "Get one secret scanning alert by id (sec_…), with every place it was found, its activity, its bypass requests, and whether you may bypass it or only ask to.",
177− SecurityOp::UpdateSecretAlert => "Dismiss a secret scanning alert (state dismissed, with a reason: false_positive, used_in_tests, revoked or wont_fix, and an optional comment) or reopen it (state open). Revoked marks it fixed; the others let pushes carrying it through. Takes the Admin role.",
177+ SecurityOp::UpdateSecretAlert => "Dismiss a secret scanning alert (state dismissed, with a reason: false_positive, used_in_tests, revoked or wont_fix, and an optional comment) or reopen it (state open). Revoked marks it fixed; the others let pushes carrying it through. Takes the Write role, or a security manager of the workspace.",
178178 SecurityOp::ListSecretLocations => "List every place a secret was found: file, line, commit and whether a push or the history scan found it.",
179179 SecurityOp::BypassPushProtection => "Push past push protection for a blocked secret, with a reason: false_positive or used_in_tests (the alert is closed with that reason) or will_fix_later (it stays open, to be rotated). Recorded on the alert and in the audit log. With delegated bypass on, someone who does not review bypasses makes a request instead, which owners and the repository's admins approve or deny; the answer says which happened. Push again once it is bypassed or approved.",
180180 SecurityOp::CheckSecretValidity => "Ask a landed secret's issuer whether it still works, and mark the alert active or inactive. The check is the issuer's own read-only identity call over HTTPS; the secret goes nowhere else. Needs validity checks on for the workspace (and the Security and quality activation on a private repository). Formats with no safe check answer unsupported.",
181181 SecurityOp::ListBypassRequests => "List a workspace's push protection bypass requests, pending first. Owners and repository admins see every request; anyone else their own. Filter by state (pending, approved, denied, cancelled) or repo.",
182− SecurityOp::ReviewBypassRequest => "Approve or deny a bypass request (owners and the repository's admins, never your own), or cancel your own. An approved request bypasses push protection for that secret, as its requester asked.",
182+ SecurityOp::ReviewBypassRequest => "Approve or deny a bypass request (owners, security managers and the repository's admins, never your own), or cancel your own. An approved request bypasses push protection for that secret, as its requester asked.",
183183 SecurityOp::ListCustomPatterns => "List custom secret patterns: a repository's own and the ones it inherits from its workspace (with repo), or a workspace's (with workspace).",
184184 SecurityOp::CreateCustomPattern => "Create a custom secret pattern: a name, a regular expression for the secret, optional regular expressions for what comes right before and after it, and test strings. Patterns run in linear time (no look-around or back-references) and within size limits. With publish true, push protection and scans use it at once and the history is scanned again for it; otherwise it is a draft. A repository's takes Admin; a workspace's, an owner. On a private repository it needs the Security and quality activation.",
185185 SecurityOp::UpdateCustomPattern => "Change a custom pattern, publish it, or turn it back into a draft (publish false). Returns where it matched each test string.",
199199 SecurityOp::GetSbom => "Export the dependency graph as an SPDX 2.3 JSON document, in sbom. Every package is named by its package URL.",
200200 SecurityOp::CompareDependencies => "Compare the dependencies at two commits, branches or tags (basehead, as base...head): what was added and removed per lockfile, with the known vulnerabilities of what was added and whether it passes the repository's dependency review policy. Needs the Security and quality activation on a private repository.",
201201 SecurityOp::GetSettings => "Get a repository's security settings: when the Code scanning check fails, dependency review and its policy, its workspace's settings, and whether the paid features are on for it.",
202− SecurityOp::UpdateSettings => "Change a repository's security settings: code_scanning_gate (none, errors, critical, high, medium or any), dependency_review, review_fail_on (critical, high, medium, low or none), review_deny_licenses (SPDX ids) and review_comment. Takes the Maintain role. Require the Code scanning and Dependency review checks in branch protection to gate merges on them.",
202+ SecurityOp::UpdateSettings => "Change a repository's security settings: code_scanning_gate (none, errors, critical, high, medium or any), dependency_review, review_fail_on (critical, high, medium, low or none), review_deny_licenses (SPDX ids) and review_comment. Takes the Admin role, or a security manager of the workspace. Require the Code scanning and Dependency review checks in branch protection to gate merges on them.",
203203 SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings (delegated bypass, validity checks) and whether it has the Security and quality activation.",
204204 SecurityOp::UpdateWorkspaceSettings => "Turn delegated bypass and validity checks on or off for a workspace. Owners only.",
205205 SecurityOp::GetOverview => "Get a workspace's security overview: open alerts by type and severity, how many opened and closed in the last days (7 to 90, 30 by default), a daily trend, and for each repository which features are on and what is open, most in need first. Private repositories count with the Security and quality activation only.",
+9−1
339339 a("get", Op::GetWorkspace, "A workspace's details and settings"),
340340 a("create", Op::CreateWorkspace, "Create a workspace"),
341341 a("delete", Op::DeleteWorkspace, "Delete a workspace and everything in it (support can restore it for 30 days)"),
342− a("update", Op::UpdateWorkspace, "Change its name, description, base permission or who may create teams"),
342+ a("update", Op::UpdateWorkspace, "Change its name, description, base permission, who may create teams, member privileges or the two-factor requirement"),
343+ a("list_members", Op::ListMembers, "Its members, owners first, with their roles"),
344+ a("update_member", Op::UpdateMember, "Make someone an owner or a member, billing manager or security manager"),
345+ a("remove_member", Op::RemoveMember, "Remove someone from it"),
346+ a("transfer_ownership", Op::TransferOwnership, "Hand it to another member: they become an owner, you a member"),
347+ a("leave", Op::LeaveWorkspace, "Leave it yourself"),
343348 a("list_invites", Op::ListWorkspaceInvites, "Its invites"),
344349 a("invite_member", Op::InviteMember, "Invite an email address"),
345350 a("revoke_invite", Op::RevokeWorkspaceInvite, "Revoke a pending invite"),
472477 | Op::Rules(RulesOp::DeleteRepoRuleset | RulesOp::DeleteWorkspaceRuleset)
473478 | Op::DeleteWorkspace
474479 | Op::UpdateWorkspace
480+ | Op::RemoveMember
481+ | Op::TransferOwnership
482+ | Op::LeaveWorkspace
475483 | Op::DeleteRepo
476484 | Op::PurgeRepo
477485 | Op::TransferRepo
+73−28
11 ---
22 title: Access and roles
3−description: The five repository roles and what each can do, the base permission members get, roles through teams, outside collaborators and invitations, and what agents may do on a person's behalf.
3+description: The five repository roles and what each can do, the base permission members get, the Admin role a repository's creator gets, security managers, roles through teams, outside collaborators and invitations, and what agents may do on a person's behalf.
44 ---
55
66 Everyone who can work in a repository has a role on it. The role says what
1515 | Role | For |
1616 | --- | --- |
1717 | **Read** | Read and clone; open issues and pull requests, and comment. |
18−| **Triage** | Read, and manage issues and pull requests: label, assign, close. |
19−| **Write** | Triage, and push, merge, and put agents to work. |
20−| **Maintain** | Write, and manage the repository's settings and branch protection. |
21−| **Admin** | Everything: webhooks, secrets, deployments, who has access, and the repository's name, visibility and archiving. |
18+| **Triage** | Read, and manage issues and pull requests: apply labels and milestones, assign, close. |
19+| **Write** | Triage, and push, merge, manage labels and milestones, see security alerts, and put agents to work. |
20+| **Maintain** | Write, and manage the repository's settings and topics. |
21+| **Admin** | Everything: branch protection and rulesets, webhooks, secrets, deployments, security settings, who has access, and the repository's name, visibility and archiving. |
2222
2323 Each role has everything the one above it has.
2424
2828 | --- | --- | --- | --- | --- | --- |
2929 | See code, issues and pull requests; clone and fetch | Yes | Yes | Yes | Yes | Yes |
3030 | Open issues and pull requests, and comment | Yes | Yes | Yes | Yes | Yes |
31−| Label, assign, close and reopen issues and pull requests | | Yes | Yes | Yes | Yes |
31+| Apply labels and milestones; assign, close and reopen issues and pull requests | | Yes | Yes | Yes | Yes |
3232 | Push to branches that are not protected | | | Yes | Yes | Yes |
3333 | Merge pull requests and use the merge queue | | | Yes | Yes | Yes |
34+| Create, edit and delete labels and milestones | | | Yes | Yes | Yes |
35+| See and dismiss security alerts | | | Yes | Yes | Yes |
3436 | Assign agents and start runs, plans and workflows | | | Yes | Yes | Yes |
3537 | Change the description, topics, and pull request and agent settings | | | | Yes | Yes |
36−| Change branch protection and guardrails | | | | Yes | Yes |
38+| Change branch protection, rulesets and guardrails | | | | | Yes |
39+| Change security settings, custom patterns and bypass reviews | | | | | Yes |
3740 | Manage webhooks, secrets, variables, deployments and domains | | | | | Yes |
3841 | Manage who has access, and invitations | | | | | Yes |
39−| Rename, archive, change visibility and the default branch | | | | | Yes |
40−| Transfer or delete the repository | | | | | Owners only |
42+| Rename, archive and change the default branch | | | | | Yes |
43+| Change visibility | | | | | Yes, if the [member privileges](/guides/workspaces/#member-privileges) allow |
44+| Transfer or delete the repository | | | | | Owners, or Admins if the member privileges allow |
4145
42−Transferring and deleting a repository also need an owner of its
43−workspace: someone given Admin on one repository cannot do either. Whoever
44−opened an issue or pull request can still edit and close their own,
45−whatever their role.
46+Changing a repository's visibility, transferring it and deleting it also
47+depend on its workspace's [member privileges](/guides/workspaces/#member-privileges).
48+By default, a member with Admin can change visibility, and only an owner
49+can transfer or delete. Someone given Admin on one repository without being
50+a member, an outside collaborator, can do none of the three.
51+
52+Some things work a little differently on g1t:
4653
54+- Whoever opened an issue or pull request can still edit, label and close
55+ their own, whatever their role.
56+- A [protected branch](/guides/git/#protected-branches) takes no pushes
57+ from anyone, Maintain and Admin included. To let a role push, list it as
58+ a bypass actor of a [ruleset](/guides/rules/) instead.
59+- Applying a label the repository does not have yet creates it, for
60+ someone with Write.
61+
4762 Read and Triage cannot put agents to work, or start anything else that
4863 spends compute: runs, plans, workflows and deployments need Write.
4964
5772 workspace's [base permission](#the-base-permission) on every repository
5873 in it.
5974 3. **A role given to you on that repository.** See
60− [add someone to a repository](#add-someone-to-a-repository).
61−4. **Your teams.** The role each [team](/guides/teams/) you are in has on
75+ [add someone to a repository](#add-someone-to-a-repository). Whoever
76+ creates a repository is given Admin on it this way, so it stays theirs
77+ to run whatever the base permission is.
78+4. **Security manager.** A member who is one of the workspace's
79+ [security managers](/guides/workspaces/#roles-that-add-to-a-member)
80+ has Read on every repository, and can see and manage its security
81+ alerts and security settings whatever their role.
82+5. **Your teams.** The role each [team](/guides/teams/) you are in has on
6283 that repository, and the roles of that team's parent teams, which child
6384 teams inherit. See [repository access](/guides/teams/#repository-access).
64−5. **Public.** Anyone, signed in or not, can read a public repository.
85+6. **Public.** Anyone, signed in or not, can read a public repository.
6586
6687 The highest wins. A member whose base permission is Read and who is given
6788 Maintain on one repository has Maintain there and Read everywhere else. A
83104 and a message naming the role they need:
84105
85106 ```text
86−You need the Maintain role or higher on acme/rocket to do that.
107+You need the Admin role or higher on acme/rocket to do that.
87108 ```
88109
89110 ### Git
105126 | Base permission | Members get |
106127 | --- | --- |
107128 | **None** | Nothing beyond what is public. Members see only the private repositories they are given a role on. |
108−| **Read** | Read on every repository. |
109−| **Write** | Write on every repository. The default. |
110−| **Admin** | Admin on every repository. Transferring and deleting stay with owners. |
129+| **Read** | Read on every repository. What a new workspace starts with. |
130+| **Write** | Write on every repository. |
131+| **Admin** | Admin on every repository. Transferring and deleting stay with owners unless the member privileges allow them. |
111132
112133 Owners always have Admin, whatever it says. Only owners can change it:
113134
116137
117138 It takes effect on everyone's next request. To give one member more on
118139 one repository, give them a role there; to give them less, lower the base
119−permission and give roles to the people who need them.
140+permission and give roles to the people who need them. Whoever creates a
141+repository keeps Admin on it however low the base permission is.
120142
121143 ### What changed for existing members
122144
145+A workspace made from 2026-10-08 starts at **Read**. One made before keeps
146+the base permission it had, **Write** unless an owner changed it.
147+
123148 Before roles, every member of a workspace could also change a repository's
124149 settings, its branch protection and guardrails, webhooks, secrets and
125−variables, deployments and domains. With the default base permission,
126−Write, members keep pushing, merging and putting agents to work; changing
127−settings and protection now needs Maintain, and the rest Admin. Owners
150+variables, deployments and domains. With Write, members keep pushing,
151+merging and putting agents to work; changing settings now needs Maintain,
152+and branch protection, rulesets, guardrails and the rest Admin. Owners
128153 have Admin, so they keep all of it.
129154
155+On 2026-10-08 the roles were brought in line with the table above:
156+
157+- Branch protection, rulesets and guardrails moved from Maintain to Admin.
158+- Creating, editing and deleting labels and milestones moved from Triage
159+ to Write; Triage still applies them.
160+- Seeing and dismissing security alerts, secrets included, takes Write;
161+ changing security settings and custom patterns takes Admin.
162+- Whoever made each existing repository and is still a member of its
163+ workspace was given Admin on it, unless they had it already.
164+
130165 To give members everything they had before, an owner sets the base
131166 permission to **Admin**. They then also get what only owners could do
132167 before: managing who has access, renaming and archiving repositories, and
147182 teams given a role on it, with how many people each has. People with Write
148183 or Maintain can see the lists; changing them needs Admin.
149184
185+Giving a role to someone outside the workspace takes an owner when the
186+workspace's [member privileges](/guides/workspaces/#member-privileges) turn
187+off **Repository admins can add outside collaborators**.
188+
150189 Someone with Admin can give a team a role under **Teams with access**:
151190 pick the team and its role, and add it. Only the workspace's own teams can
152191 be added. See [teams](/guides/teams/#repository-access).
203242 (see [members and roles](/guides/workspaces/#members-and-roles)); the
204243 roles they have stay, and the base permission adds to them.
205244
206−Removing a member from a workspace also removes the roles they were given
207−on its repositories, and takes them out of its teams.
245+Removing a member from a workspace, or their leaving it, also removes the
246+roles they were given on its repositories, and takes them out of its teams.
247+
248+A workspace that [requires two-factor authentication](/guides/authentication/#require-two-factor-authentication)
249+holds its outside collaborators to it as it does its members: without it,
250+they cannot reach its repositories until they turn it on.
208251
209252 ## Invitations
210253
274317 "username": "ada",
275318 "role": "write",
276319 "source": "base",
277− "capabilities": ["read", "participate", "triage", "push", "merge", "run"]
320+ "capabilities": ["read", "participate", "triage", "push", "merge", "manage_labels", "security_alerts", "run"]
278321 }
279322 ```
280323
299342
300343 The workspace's [audit log](/guides/audit-log/) records the same changes
301344 under those names, and also `repo.invitation_created`,
302−`repo.invitation_revoked` and `workspace.base_permission_changed`.
345+`repo.invitation_revoked` and `workspace.base_permission_changed`. A
346+repository's creator being given Admin is not recorded: it comes with
347+`repo.created`.
303348
304349 A team's role on a repository changing is sent as `team.repo_added`,
305350 `team.repo_role_changed` or `team.repo_removed`; see
+15−2
3131 | `repo.collaborator_added`, `repo.collaborator_role_changed`, `repo.collaborator_removed` | Someone was given a role on it, had it changed, or lost it. See [access and roles](/guides/access-and-roles/). |
3232 | `repo.invitation_created`, `repo.invitation_revoked` | Someone was invited to it, or an invitation was withdrawn. |
3333 | `workspace.base_permission_changed` | An owner changed what members get on every repository. |
34+| `member.added`, `member.removed`, `member.left` | Someone joined the workspace (added by an owner, or with an invite), was removed by an owner, or left. See [members and roles](/guides/workspaces/#members-and-roles). |
35+| `member.role_changed` | An owner made someone an owner or a member. |
36+| `member.org_role_added`, `member.org_role_removed` | An owner made someone a billing manager or a security manager, or took it away. |
37+| `workspace.ownership_transferred` | An owner handed the workspace to another member. |
38+| `workspace.member_privileges_changed` | An owner turned a [member privilege](/guides/workspaces/#member-privileges) on or off. |
39+| `workspace.two_factor_required`, `workspace.two_factor_not_required` | An owner started or stopped [requiring two-factor authentication](/guides/workspaces/#require-two-factor-authentication). |
40+| `workspace_token.created`, `workspace_token.deleted` | An owner made or deleted one of the workspace's [access tokens](/guides/workspaces/#workspace-access-tokens). |
41+| `token.created`, `token.deleted`, `token.rescoped` | A member made, deleted or changed the scopes of one of their own [access tokens](/guides/authentication/#access-tokens). Recorded in each of their workspaces. |
42+| `ssh_key.added`, `ssh_key.removed` | A member added or removed an SSH key. Recorded in each of their workspaces. |
43+| `oauth_grant.created`, `oauth_grant.rescoped`, `oauth_grant.revoked` | A member [signed in to an application](/guides/authentication/#signing-in-with-oauth), changed what it may do, or signed it out. Recorded in each of their workspaces. |
44+| `two_factor.enabled`, `two_factor.disabled` | A member turned [two-factor authentication](/guides/authentication/#two-factor-authentication) on or off. Recorded in each of their workspaces. |
3445 | `workspace.team_creation_changed` | An owner changed who can create teams. See [who can create teams](/guides/teams/#who-can-create-teams). |
3546 | `team.created`, `team.edited`, `team.deleted` | A [team](/guides/teams/) was created, changed or deleted. |
3647 | `team.member_added`, `team.member_role_changed`, `team.member_removed` | Someone was added to a team, made its maintainer or a member, or taken out of it. |
116127 ## What is not recorded
117128
118129 - Reads by people and workspace tokens.
119−- What people do on the website itself. The API, the MCP server and git
120− are recorded.
130+- Most of what people do on the website itself. The API, the MCP server
131+ and git are recorded, and so are the changes to members, roles, access,
132+ tokens, keys, applications, two-factor authentication and workspace
133+ settings in the table above, wherever they are made.
121134 - What g1t does on its own, such as closing a pull request whose agent
122135 failed. Those changes are in the pull request's timeline.
+79−7
11 ---
22 title: Accounts and authentication
3−description: Accounts, invites, email addresses, confirming them, personal access tokens and their scopes, OAuth, signing in from a tool, password reset and your security log.
3+description: Accounts, invites, email addresses, confirming them, two-factor authentication and recovery codes, personal access tokens and their scopes, OAuth, signing in from a tool, password reset and your security log.
44 ---
55
66 ## Creating an account
3030 | Access tokens | [`/settings/tokens`](https://g1t.sh/settings/tokens) | Your [personal access tokens](#access-tokens). |
3131 | GitHub | [`/settings/github`](https://g1t.sh/settings/github) | [Linking and unlinking GitHub](/guides/github/#link-and-unlink-github). |
3232 | Connected applications | [`/settings/applications`](https://g1t.sh/settings/applications) | Tools you [signed in to with OAuth](#signing-in-with-oauth), such as an agent using the MCP server. |
33+| Two-factor authentication | [`/settings/two-factor`](https://g1t.sh/settings/two-factor) | [An authenticator app and recovery codes](#two-factor-authentication). |
3334 | Security log | [`/settings/security-log`](https://g1t.sh/settings/security-log) | [What happened to your account](#security-log). |
3435
3536 `g1t.sh/settings` opens Profile.
4445 Making an account with GitHub needs an invite too: start from your invite
4546 link, or enter the code when g1t asks for it after GitHub.
4647
48+With [two-factor authentication](#two-factor-authentication) on, signing in
49+with GitHub asks for a code from your app as well.
50+
51+## Two-factor authentication
52+
53+Two-factor authentication asks for a code from an authenticator app on
54+your phone each time you sign in with your password or with GitHub, so a
55+stolen password is not enough. Any app that reads a time-based one-time
56+password (TOTP) QR code works, such as 1Password, Google Authenticator or
57+Authy.
58+
59+### Turn it on
60+
61+1. Open [Settings → Two-factor authentication](https://g1t.sh/settings/two-factor)
62+ and choose **Set up**. g1t asks for your password if you have not
63+ signed in in the last 10 minutes.
64+2. Scan the QR code with your app, or type the key shown under it.
65+3. Enter the six-digit code the app shows, and choose **Turn on**.
66+4. Save the ten recovery codes g1t shows. They are shown only then.
67+
68+### Signing in with it on
69+
70+After your password (or GitHub), g1t asks for the code from your app. A
71+code works for 30 seconds, and the one before and after it are accepted
72+too, for a phone clock a little off. Each code works once. After five wrong
73+codes, or ten minutes, start the sign-in again.
74+
75+Lost your phone? Enter a recovery code instead of the app's code. Each
76+works once, and your security log records its use.
77+
78+Git over HTTPS never takes your password while two-factor authentication
79+is on: use a [personal access token](#access-tokens) as the password, or
80+[SSH](/guides/git/). Access tokens, SSH keys and OAuth applications are
81+not affected.
82+
83+### Recovery codes, and turning it off
84+
85+On the same page:
86+
87+- **Make new recovery codes** replaces all ten; the old ones stop working.
88+- **Turn off** needs a code from your app or a recovery code, and your
89+ password if you have not signed in in the last 10 minutes.
90+
91+You cannot turn it off while you own a workspace that
92+[requires it](/guides/workspaces/#require-two-factor-authentication): stop
93+requiring it there first, or hand the workspace to another owner. In a
94+workspace that requires it, turning it off holds you out of that workspace
95+until you turn it on again.
96+
97+Turning it on or off, and making new recovery codes, are emailed to your
98+primary and backup addresses, written to your [security log](#security-log),
99+and recorded in the [audit log](/guides/audit-log/) of each of your
100+workspaces as `two_factor.enabled` and `two_factor.disabled`.
101+
102+### Require two-factor authentication
103+
104+An owner can require it of everyone with access to a workspace. See
105+[Workspaces](/guides/workspaces/#require-two-factor-authentication).
106+
107+Passkeys are not supported yet; they are next.
108+
47109 ## Invites
48110
49111 While g1t is invite-only, every new account needs an invite code, such as
183245
184246 ### Confirming it is you
185247
186−Adding or removing an address, and changing your primary or backup, need
187−proof that it is you: a sign-in in the last 10 minutes, or your password,
248+Adding or removing an address, changing your primary or backup, and
249+turning two-factor authentication on or off, need proof that it is you: a
250+sign-in in the last 10 minutes, or your password,
188251 which g1t asks for on the page. After you enter it, g1t does not ask again
189252 for 10 minutes. An account that signs in only with GitHub signs out and in
190253 with GitHub again, or sets a password with
596659
597660 [Settings → Security log](https://g1t.sh/settings/security-log) lists what
598661 happened to your account: addresses added, confirmed, removed or made
599−primary, your backup and privacy settings, password changes, and pauses
600−after too many wrong passwords. Changes g1t staff made, such as removing an
601−address someone else needed, say so and why.
662+primary, your backup and privacy settings, password changes, pauses after
663+too many wrong passwords, two-factor authentication turned on or off and
664+recovery codes made or used, personal access tokens created, deleted or
665+given new scopes, SSH keys added or removed, and applications authorized,
666+changed or revoked. Changes g1t staff made, such as removing an address
667+someone else needed, say so and why.
668+
669+Token, SSH key, application and two-factor changes are also recorded in the
670+[audit log](/guides/audit-log/) of each workspace you belong to, where its
671+owners see them.
602672
603673 ## What g1t stores
604674
605675 Passwords are stored as salted PBKDF2-SHA256 hashes. Sessions and tokens are
606−stored as SHA-256 hashes. Neither can be read back.
676+stored as SHA-256 hashes. Neither can be read back. A two-factor secret is
677+encrypted (AES-256-GCM) and bound to your account, and recovery codes are
678+kept as SHA-256 hashes.
+2−2
240240
241241 ## Require review from code owners
242242
243−Someone with the Maintain role or higher turns it on under the
243+Someone with the Admin role turns it on under the
244244 repository's **Settings → Rules**, in a ruleset's **Require a pull request before merging** rule:
245245 **Require review from code owners**. It is off by default. From the API it
246246 is the `pull_request` rule's `require_code_owner_review` (see [rules](/guides/rules/)).
333333 | --- | --- | --- |
334334 | `GET /repos/{owner}/{name}/codeowners/errors` | `repository` `codeowners` | The file at `ref` (the default branch when left out): where it is, its size, its rules and sections, and every error. Needs `repo:read`. |
335335 | `GET /repos/{owner}/{name}/pulls/{number}` | `pull_request` `get` | `code_owners`: the file's path, `required`, a review per rule with `section`, `pattern`, `owners`, `files`, `required`, `approved_by`, `changes_requested_by` and `satisfied`, what is `missing`, and how many `errors` the file has. Absent when the target has no file. |
336−| `PATCH /repos/{owner}/{name}/settings` | `repository` `update_settings` | `require_code_owner_review`: `true` or `false`. Needs Maintain. |
336+| `PATCH /repos/{owner}/{name}/settings` | `repository` `update_settings` | `require_code_owner_review`: `true` or `false`. Needs Admin. |
337337
338338 ```sh
339339 curl "https://api.g1t.sh/repos/acme/api/codeowners/errors?ref=main" \
+2−2
2323 - **Workspace defaults**: the workspace's **Settings**, **Guardrails**.
2424 Owners can change them; members can read them.
2525 - **A project's overrides**: the project's **Settings**, **Guardrails**.
26− People with the Maintain [role](/guides/access-and-roles/) or higher on
26+ People with the Admin [role](/guides/access-and-roles/) on
2727 its repository can see and change them; the page is not shown to anyone
2828 else.
2929
149149 no environments.
150150
151151 Workflow-only domains are set by the same people as the rest of the page:
152−owners for the workspace's, Maintain or higher for a project's. Each change
152+owners for the workspace's, Admin for a project's. Each change
153153 is recorded in the workspace's [audit log](/guides/audit-log/) as
154154 `update_guardrails`, saying which domains were added or removed and what
155155 they were limited to.
+2−2
5656 An issue or a pull request carries at most 20 labels.
5757
5858 You can label an issue as you open it, too: tick labels on **New issue**,
59−or with the Triage role, type new ones beside them.
59+or with the Write role, type new ones beside them.
6060
6161 ## Filter by a label
6262
7373 requests carry it; choose a count to see them. Search finds a label by its
7474 name or description.
7575
76−With the Triage role or higher you can:
76+With the Write role or higher you can (applying labels needs only Triage):
7777
7878 | To | Do this |
7979 | --- | --- |
+4−3
110110 | Default branch | Admin |
111111 | Rename a branch | Write; Admin for the default branch |
112112 | Rename the repository | Admin |
113−| Make it public or private | Admin |
113+| Make it public or private | Admin, and the [member privileges](/guides/workspaces/#member-privileges) to allow it, or an owner |
114114 | Archive or unarchive | Admin |
115−| [Transfer](/guides/transferring-repositories/) | An owner of both workspaces |
116−| Delete, restore and purge | An owner of its workspace |
115+| [Transfer](/guides/transferring-repositories/) | An owner of its workspace, or a member with Admin when the member privileges allow it; and in the other workspace, being able to create a repository |
116+| Delete | An owner of its workspace, or a member with Admin when the member privileges allow it |
117+| Restore and purge | An owner of its workspace |
117118 | See the Recently deleted list | An owner of its workspace |
118119
119120 Renaming the repository or its default branch, changing its visibility
+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

This change is too large to show in full.