| 1 | # 0023 — Organization access is a base permission, raised per repository |
| 2 | |
| 3 | **Status:** adopted; supersedes the organization half of |
| 4 | [0022](0022-owners-share-one-namespace.md) |
| 5 | |
| 6 | ## Decision |
| 7 | |
| 8 | An organization role says what someone may do *to the organization*; it no |
| 9 | longer doubles as their role on its repositories. |
| 10 | |
| 11 | | Organization role | On every repository | Runs the organization | |
| 12 | |-------------------|---------------------|-----------------------| |
| 13 | | `owner` | `owner |
| 14 | | `admin` | `admin |
| 15 | | `member` | the base permission | nothing |
| 16 | |
| 17 | The **base permission** (` |
| 18 | repository role, or none. On top of it, a member is raised by the roles their |
| 19 | teams hold on a repository (`repository_team`) and by their own collaborator |
| 20 | row; the highest wins ([0021](0021-access-is-a-role-ladder.md |
| 21 | permission at none, a private repository is visible only to its teams and |
| 22 | collaborators, which is how a private repository is kept to one team. |
| 23 | |
| 24 | The same `organization_settings` row holds the member policies (whether members |
| 25 | may create public or private repositories, whether private repositories may be |
| 26 | forked, the default branch recorded on new repositories) and the public profile |
| 27 | (description, website, location, email). It is written when the organization is |
| 28 | created. |
| 29 | |
| 30 | ## Around it |
| 31 | |
| 32 | - **Outside collaborators:** people with collaborator rows on the organization's |
| 33 | repositories who are not members, listed and removable from all of them at |
| 34 | once. |
| 35 | - **Membership is private** unless a member publicizes it |
| 36 | (`organization_public_member`); outsiders see public members only, and a |
| 37 | user's public organizations. |
| 38 | - **Team maintainers** ( |
| 39 | administering the organization. Team membership is written by Ghost rather |
| 40 | than through Better Auth, whose checks know only organization roles; the rows |
| 41 | keep Better Auth's own dedupe key and `memberCount`. |
| 42 | - **Teams** live at |
| 43 | are mentioned as `@org/team`. |
| 44 | - **Transfers** to the requester's own account or an organization they |
| 45 | administer happen at once; any other recipient accepts first |
| 46 | (`repository_transfer`). Renames and transfers leave a `repository_redirect`, |
| 47 | which the access service follows when a name no longer matches a live |
| 48 | repository, so links and git remotes survive. |
| 49 | |
| 50 | ## Why |
| 51 | |
| 52 | GitHub separates the two for a reason: tying an organization role to repository |
| 53 | access meant every member could read every private repository, and nobody could |
| 54 | be admitted to one repository without being admitted to all of them. |
| 55 | |
| 56 | ## Consequences |
| 57 | |
| 58 | - Migration `0026` maps every existing member role other than `owner` and |
| 59 | `admin` to `member`, and gives every organization the default base permission, |
| 60 | `read`. |
| 61 | - `settings` and `teams` are reserved repository slugs, since |
| 62 | `/<owner>/settings` and `/<org>/teams` are pages of the owner's own. |
| 63 | - A redirect lands on the repository's root in the web app; the path below it |
| 64 | is not carried over. |