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.
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 1 | // Namespaces follow GitHub's rules (no leading, trailing or doubled hyphens), |
| 2 | // so "--" can never appear inside one. | |
| 3 | const NAMESPACE = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/; | |
| 4 | const REPO_NAME = /^[a-z0-9._-]{1,100}$/; | |
| 5 | ||
| 6 | /** Routes and reserved words that may not be registered as usernames. */ | |
| 7 | const RESERVED = new Set([ | |
| 8 | "api", "mcp", "login", "logout", "register", "new", "settings", "search", | |
| Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036) | 9 | "admin", "auth", "integrations", "pulls", "issues", "verify", "confirm-email", "forgot", "reset", "device", "workspaces", "u", "oauth", "assets", "avatars", "docs", "explore", "about", "pricing", |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 10 | // g1t itself, and the name its agent once went by: everything g1t does is |
| Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037) | 11 | // shown as `g1t`, so nobody else may be called either. `ghost` wrote what |
| 12 | // a deleted account wrote (`GHOST_USERNAME`). | |
| 13 | "g1t", "g1t-agent", "ghost", | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 14 | // Trust pages on g1t.sh, and names kept for them. |
| 15 | "policies", "security", "support", "status", "terms", "privacy", "help", "blog", | |
| 16 | // Invite links, and the waitlist. | |
| 17 | "invite", "invites", "waitlist", | |
| 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) | 18 | // g1t.sh/invitations: the workspace invitations waiting for an answer. |
| 19 | "invitations", | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 20 | // The container registry, at g1t.sh/v2/. |
| 21 | "v2", | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 22 | // g1t.sh/inbox, and the name it might also go by. |
| 23 | "inbox", "notifications", | |
| preparing for cloud work | 24 | // g1t.sh/download, the desktop app; g1t.sh/downloads/, the releases it and the runner come from. |
| 25 | "download", "downloads", | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 26 | ]); |
| 27 | ||
| A workflow's `uses: actions/setup-node@v4` means GitHub's `actions` again, whoever is on g1t. g1t looked for a repository on g1t before GitHub, and anyone could make a public workspace called `actions`, `docker` or `aws-actions` and have its `setup-node` run in every workflow that names GitHub's, with that job's secrets. Now another workspace's public repository answers only for a name GitHub has no public repository of; the caller's own workspace and g1t's own `g1t/…` still answer from g1t, and when GitHub can't be asked, GitHub's is used. The names of the GitHub accounts whose actions workflows use most can no longer be registered, renamed to, or taken by signing up with GitHub (which suggests the name with `-gh`); an account that has one keeps it, and mentions and invitations still find it. The Actions, authentication and workspaces guides say so. | 28 | /** |
| 29 | * The GitHub accounts whose actions workflows use most. `uses: | |
| 30 | * actions/setup-node@v4` means GitHub's `actions`, so nobody may register | |
| 31 | * these on g1t; an account that already has one keeps it. The list is | |
| 32 | * crates/contracts/src/names.rs's `ACTION_OWNERS`. | |
| 33 | */ | |
| 34 | const ACTION_OWNERS = new Set([ | |
| 35 | "actions", "actions-ecosystem", "actions-rs", "amannn", "anchore", "appleboy", "aquasecurity", | |
| 36 | "arduino", "astral-sh", "aws-actions", "azure", "browser-actions", "bufbuild", "cachix", | |
| 37 | "changesets", "chromaui", "cloudflare", "codecov", "conda-incubator", "coursier", "crazy-max", | |
| 38 | "cypress-io", "dawidd6", "denoland", "dependabot", "dessant", "determinatesystems", | |
| 39 | "devcontainers", "digitalocean", "docker", "dorny", "dtolnay", "endbug", "erlef", | |
| 40 | "firebaseextended", "fluxcd", "getsentry", "github", "golangci", "google-github-actions", | |
| 41 | "googleapis", "goreleaser", "gradle", "gradle-update", "hadolint", "hashicorp", | |
| 42 | "haskell-actions", "hecrj", "helm", "ilammy", "jamesives", "jetbrains", "julia-actions", | |
| 43 | "kubernetes-sigs", "lukka", "marocchino", "microsoft", "mikepenz", "mozilla-actions", "msys2", | |
| 44 | "mxschmitt", "ncipollo", "netlify", "nick-fields", "nwtgck", "octokit", "ossf", "oven-sh", | |
| 45 | "peaceiris", "peter-evans", "pnpm", "pre-commit", "pypa", "r-lib", "release-drafter", | |
| 46 | "renovatebot", "reviewdog", "rhysd", "ruby", "rust-lang", "sbt", "shivammathur", "sigstore", | |
| 47 | "slackapi", "slsa-framework", "snyk", "softprops", "sonarsource", "stefanzweifel", | |
| 48 | "step-security", "styfle", "subosito", "supabase", "super-linter", "superfly", "svenstaro", | |
| 49 | "swatinem", "taiki-e", "tj-actions", "treosh", "vercel", "webfactory", | |
| 50 | ]); | |
| 51 | ||
| 52 | /** Whether `value`, whatever its case, names a well-known GitHub action owner. */ | |
| 53 | export function isActionOwnerName(value: string): boolean { | |
| 54 | return ACTION_OWNERS.has(value.trim().toLowerCase()); | |
| 55 | } | |
| 56 | ||
| 57 | /** Whether a person or workspace may take `value` as its name now: valid, and no well-known action owner's. */ | |
| 58 | export function isClaimableName(value: string): boolean { | |
| 59 | return isValidNamespace(value) && !isActionOwnerName(value); | |
| 60 | } | |
| 61 | ||
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 62 | /** Whether `value`, whatever its case, is a name nobody can register: a route, or g1t's own. */ |
| 63 | export function isReservedName(value: string): boolean { | |
| 64 | return RESERVED.has(value.trim().toLowerCase()); | |
| 65 | } | |
| 66 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 67 | export function isValidNamespace(value: string): boolean { |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 68 | return isNamespaceShaped(value) && !isReservedName(value); |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Whether `value` has a namespace's shape, reserved or not: what a | |
| 73 | * workspace's old name or an alias staff set (such as `g1t`) can be. | |
| 74 | */ | |
| 75 | export function isNamespaceShaped(value: string): boolean { | |
| 76 | return NAMESPACE.test(value); | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 77 | } |
| 78 | ||
| 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 | 79 | /** |
| 80 | * Usernames: letters of either case, digits and single hyphens, not | |
| 81 | * starting or ending with a hyphen, 1 to 39 characters, never reserved in | |
| 82 | * any case. The case is kept for showing; everything finds a person by the | |
| 83 | * name lowercased (`canonicalUsername`), so `Ana` and `ana` are one name. | |
| 84 | */ | |
| 85 | const USERNAME = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/; | |
| 86 | ||
| 87 | /** The `pattern` a username field takes in a form: the same rule, for the browser. */ | |
| 88 | export const USERNAME_PATTERN = "[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9]))*"; | |
| 89 | ||
| 90 | export function isValidUsername(value: string): boolean { | |
| 91 | const name = value.trim(); | |
| A workflow's `uses: actions/setup-node@v4` means GitHub's `actions` again, whoever is on g1t. g1t looked for a repository on g1t before GitHub, and anyone could make a public workspace called `actions`, `docker` or `aws-actions` and have its `setup-node` run in every workflow that names GitHub's, with that job's secrets. Now another workspace's public repository answers only for a name GitHub has no public repository of; the caller's own workspace and g1t's own `g1t/…` still answer from g1t, and when GitHub can't be asked, GitHub's is used. The names of the GitHub accounts whose actions workflows use most can no longer be registered, renamed to, or taken by signing up with GitHub (which suggests the name with `-gh`); an account that has one keeps it, and mentions and invitations still find it. The Actions, authentication and workspaces guides say so. | 92 | return USERNAME.test(name) && !isReservedName(name) && !isActionOwnerName(name); |
| 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 | 93 | } |
| 94 | ||
| 95 | /** The name a person is found, linked and mentioned by: lowercased. */ | |
| 96 | export function canonicalUsername(value: string): string { | |
| 97 | return value.trim().toLowerCase(); | |
| 98 | } | |
| 99 | ||
| 100 | /** How a person's username shows: as they wrote it, or as it is kept. */ | |
| 101 | export function shownUsername(person: { username: string; display_username?: string | null; displayUsername?: string | null }): string { | |
| 102 | const display = person.display_username ?? person.displayUsername; | |
| 103 | return display && display.toLowerCase() === person.username.toLowerCase() ? display : person.username; | |
| 104 | } | |
| 105 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 106 | export function isValidRepoName(value: string): boolean { |
| 107 | return ( | |
| 108 | REPO_NAME.test(value) && | |
| 109 | !value.startsWith(".") && | |
| 110 | !value.endsWith(".git") && | |
| 111 | // `/<workspace>/-/…` holds the workspace's own pages. | |
| 112 | value !== "-" | |
| 113 | ); | |
| 114 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.