Skip to content
273 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Billing accounts, terms and enterprises; g1t is no longer free1# sudo
2
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales3g1t's staff console, at <https://sudo.g1t.sh>: the back office g1t is
4building for itself, for sales, support and finance. It is organised the
5way customers know g1t: by **workspace**.
6
7The sidebar (`app/lib/nav.ts`) has Overview and Reach out at the top, then
8sections that fold open to their pages: Customers, Revenue, Platform,
9Support and Team. Each fold is a `<details>`, drawn open for the section
10holding the current page; on a phone the same menu sits behind a "Menu"
11button in the top bar. Pages not built yet are marked **Soon**: each is a
12real page (`routes/soon.tsx`, made from its entry in `nav.ts`) saying what
13it will do, why, and what it will have, so the sidebar doubles as the
14roadmap.
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace15
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales16- **Overview** (`/`): this month charged, cost and margin; the last six
17 months as a chart; this month by kind of usage; paying workspaces; how
18 many are stopped, near their limit or declined (each a link into Reach
19 out); open invoices; follow-ups due; and the five most urgent signals.
20 From billing's `admin_overview` and `admin_signals`.
21- **Reach out** (`/reach-out`): every workspace worth a word, most urgent
22 first (at limit, declined, near limit, high spend, growing, established,
23 first payment), with its owners, the reason in a sentence, the figure,
24 and its sales stage and owner at g1t. Filter by why and by whose
sudo: Invoices, Audit log, and follow-ups due25 (everyone's, unassigned, mine), or show only **follow-ups due** (a next
26 step due today or earlier, on a deal not won or lost; one per
27 workspace). Rows show the next step and its day. Each row opens the
28 workspace's Sales.
29- **Invoices** (`/invoices`): every invoice g1t has sent, workspaces' and
30 enterprises', newest first (`admin_invoices`, at most 200). Filter by
31 status and month; totals for what is listed (amount, paid, outstanding);
32 each links to its workspace or enterprise and to Stripe's page (https
33 only).
34- **Audit log** (`/audit`): every change made in sudo, and what Stripe told
35 billing, newest first, 100 a page with "Older" (`admin_audit`). Filter by
36 staff email and kind of change; each line links to its workspace or
37 enterprise.
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales38- **Workspaces** (`/workspaces`): every workspace, newest first, 50 to a
Stripe webhooks, enterprise invoices, and sudo for both39 page, with its owners, members, who it is billed to, its terms, this
40 month's usage against its limit, what it was charged and what it cost
41 g1t. Search by workspace, owner, email or enterprise (across the whole
42 list); filter to stopped or warning, comped or custom, or on an
43 enterprise. Billing's figures are fetched for exactly the page shown, so
44 the filters, and the totals over the list, cover that page; the page
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales45 says so when there is more than one. A workspace's page shows its members;
46 **Sales** (its stage, the staff member who has it, the next step and its
47 date, and notes, newest first; `admin_sales`, `admin_set_sales`,
48 `admin_add_note`); and under **Billing** its limit in words (trust, the
49 owners' own spend limit or the default, the most they may set, how it
50 grows), its last six months as a chart, its invoices (`admin_workspace_invoices`,
51 with Stripe's page and PDF), its terms, who it is billed to (move it onto or off an
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace52 enterprise), a credit form, a Stripe billing link, its ledger and its
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales53 audit log. If billing does not answer for sales or invoices, the page
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member54 still opens and says so in those sections. A protected workspace (one
55 nobody can ever delete: identity's `PROTECTED_WORKSPACES`, and
56 flagon-io always) says so beside its name.
57- **Deleted workspaces** (`/workspaces/deleted`, linked from Workspaces):
58 workspaces their owners deleted, newest first (`admin_deleted_workspaces`),
59 each with who deleted it and when, when it is purged, what went with it
60 (repositories, projects, members, counted at the deletion) and the days
61 left. An owner deletes a workspace with everything in it in one step, and
62 identity keeps it 30 days (`WORKSPACE_RESTORE_DAYS`) so support can undo a
63 deletion that was a mistake or not theirs to make. **Restore**
64 (`admin_restore_workspace`) brings it back with its members and tokens,
65 and its repositories, projects and apps with `workspace.restored`; its
66 plan stays ended, so its owners start it again from Billing. Check that
67 whoever asks is an owner of it before restoring. **Purge now**
68 (`admin_purge_workspace`, the slug typed to confirm) removes it at once,
69 as the sweep does every 15 minutes once its 30 days are up; never for a
70 protected workspace. Both go in the workspace's audit log, as g1t, and in
71 sudo's (`workspace_restored`, `workspace_purged`), naming the staff
72 member.
Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037)73- **A person's page** (`/users/<username>`, linked from a workspace's
74 members): their addresses (remove one, with a reason they see), their
75 security log, and **Delete account**. Delete only when the person asks
76 (from one of the account's confirmed addresses) or for abuse: give the
77 reason, which goes in sudo's audit log (`account_deleted`), and type the
78 username (`admin_delete_account`). It does what deleting their own
79 account from Settings does: signs them out everywhere, ends their tokens,
80 SSH keys, deploy keys they added and applications, takes them out of
81 every workspace, team and repository, and emails their addresses that
82 staff deleted it. It is refused while the account is the **only owner of
83 a live workspace**: the page lists those workspaces instead of the form,
84 each linking to its page. Each needs another owner first (an owner makes
85 one under People), or to be deleted by its owner, which settles its
86 billing; staff never delete a customer's workspace to get an account
87 out. Accounts that can never be deleted (`g1t`, `g1t-agent`, `ghost`,
88 and whatever identity's `PROTECTED_ACCOUNTS` names, by username or id)
89 are marked **Protected** and offer no form. A deleted account's page
90 says so, with who deleted it, why, when it is purged, and **Restore** and
91 **Purge now**, as on Deleted accounts.
92- **Deleted accounts** (`/users/deleted`, linked from Workspaces):
93 accounts deleted by the person or by staff, newest first
94 (`admin_deleted_accounts`), each with who deleted it (the person, or the
95 staff member and why), when it is purged, the days left and what it
96 left (workspaces, teams, repositories, tokens, SSH keys). Identity keeps
97 each 30 days (`ACCOUNT_RESTORE_DAYS`). **Restore**
98 (`admin_restore_account`) clears the deletion and puts back the
99 memberships, teams and repository roles it left where they still exist;
100 its sessions, tokens and keys stay ended, and the person signs in with
101 their password. Check that whoever asks owns one of its addresses first.
102 **Purge now** (`admin_purge_account`, the username typed) removes it at
103 once, as the sweep does every 15 minutes once its 30 days are up: its
104 row, addresses, keys, two-factor secret, GitHub link, profile and
105 security log go; its username is kept in `deleted_users` and never given
106 out again; what it wrote shows as `ghost`. Both go in sudo's audit log
107 (`account_restored`, `account_purged`), naming the staff member. There
108 is no API route for deleting an account; only the site and sudo can.
Merge branch 'worktree-agent-a8385d293d42c913a'109- **Aliases** (`/aliases`, under Customers): names that lead to a
110 workspace, set by staff only; there is no way for a customer to make
111 one, and nothing user-facing mentions them. `g1t`, the product's name,
112 leads to `flagon-io`, Flagon, Inc. (seeded by identity's migration
113 `0029_workspace_aliases.sql`), so nobody mistakes the trading name for
114 the organization. Every address under an alias leads to the workspace:
115 pages answer with a 301 to the same page (`/g1t/g1t/issues` to
116 `/flagon-io/g1t/issues`), git over HTTPS is answered in place as the
117 workspace's repository (pushes do not follow redirects), the API and MCP
118 run the call again under the workspace's slug, and the package
119 registries answer a 301 (308 for a publish). An alias points at the
120 workspace's id, so it follows a rename; it goes when the workspace is
121 purged. Each row shows the workspace, why the alias exists, and who added
122 it and when. **Add** (`admin_set_alias`) takes the alias, the
123 workspace's slug and why: identity refuses the site's own routes
124 (`settings`, `api`…), anyone's username, a workspace's slug (deleted, or
125 held after a rename for another workspace) and an existing alias.
126 Reserved names such as `g1t` can be aliases, and an alias is nobody's to
127 register or rename a workspace to while it exists. **Remove**
128 (`admin_remove_alias`) needs a reason. Both go in sudo's audit log
129 (`alias_added`, `alias_removed`), naming the staff member. `@g1t` in
130 text still means g1t's agent: it links to how the agent works, never to
131 `/g1t`.
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace132- **Enterprises**: customers that pay for several workspaces with one
133 bill, one limit and one set of terms. Each has its workspaces (add or
Stripe webhooks, enterprise invoices, and sudo for both134 remove them), combined usage, terms, credits, ledger and audit log, and
135 **Invoices**: where they go (the billing email, which also makes its
136 Stripe customer), a "Send invoice now" button, and every invoice with
137 its status (open, paid, overdue, void), a line per workspace, and a link
138 to Stripe's hosted invoice page. An invoice also goes out on its own as
139 each month closes: one Stripe invoice, a line per workspace for what it
140 owes, net 30, emailed by Stripe.
Merge branch 'main' into actions-toolkit-oidc-artifacts141- **Agents & models** (`/agents`, under Platform): billing's model
142 catalogue, every model g1t can use (`admin_models`). **Check for new
143 models** lists each provider's models now, through the model proxy's
144 `Discovery` entrypoint (the `MODELS` binding), as the daily check does;
145 the result says what each provider listed, what is new or gone, or why a
146 provider could not be listed. **Defaults**: the model behind each of
147 Auto's tiers, the harness's background model and the AI Gateway's first
148 Claude (an available, priced Claude each, shown with what a typical run
149 costs on it), and each kind of job's starting tier and effort. A change
150 needs a reason and shows a review first, the current and new value side
151 by side with what a typical run would cost on each, before **Save**
152 (`admin_set_model_default`); runs pick it up within a minute. A default
153 that has fallen back (its model retired or no longer listed) says so.
154 **New models**: each model a check found, with its prices filled in
155 where known; confirm its name, tier and prices per million tokens (and
156 long-prompt prices) and **Approve**, or **Retire** it
157 (`admin_decide_model`). **Catalogue**: every other model with its status,
158 context, prices, typical run and when its provider last listed it, each
159 with **Retire** or **Restore**. **Checks**: the latest checks of each
160 provider. Every change names the staff member and why in the audit log
161 (account `models`). See docs/BILLING_OPERATIONS.md, "The model
162 catalogue".
Stripe webhooks, enterprise invoices, and sudo for both163- **Stripe**: whether billing's key is in test or live mode (or off), the
164 webhook Stripe calls (URL, endpoint id, events, who registered it and
165 when), and the events Stripe sent lately with what billing did with
166 each. "Register webhook" (or "Replace") has billing delete the endpoint
167 it made before, create a new one and keep its signing secret, which no
168 one sees. Do it once per mode, and again after switching to live keys.
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace169
A menu shaped by how g1t works, Soon pages for every promise, and sudo for sales170Sales changes are not money, so they have no confirmation step; they are
171still POSTs from sudo's own pages, recorded with who made them.
172
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace173Billing's internal account ids (`ws_<slug>` for a workspace's own,
174`ent_…` for an enterprise) are never shown as names; an enterprise's id
175appears only as small "Billing account id" text. Old `/accounts/…` links
176redirect to the workspace or enterprise they meant.
177
178**Cards stay on Stripe.** sudo never shows a card field. To help a customer
179update their card or see invoices, staff make a Stripe billing link on the
180workspace's page (it is recorded) and send it to the owner.
Billing accounts, terms and enterprises; g1t is no longer free181
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace182It holds no data. Workspaces, owners and members come from identity's
183staff methods (`admin_workspaces`, `admin_workspace`; `IdentityAdminApi` in
184`packages/contracts/src/identity.ts`); everything about money goes to the
185billing service's (`admin_*`, `BillingAdminApi` in
186`packages/contracts/src/billing.ts`), where each change is recorded with
187the staff member's email. Both are reached over service bindings only, and
188nothing but sudo binds to them.
Billing accounts, terms and enterprises; g1t is no longer free189
190## How it is locked
191
1921. **Cloudflare Access** sits in front of `sudo.g1t.sh` and signs people in.
1932. **The worker checks Access's work** on every request, the stylesheet
194 included (`run_worker_first`): it verifies the `Cf-Access-Jwt-Assertion`
195 JWT itself (RS256 against the team's published keys, audience, issuer,
196 expiry), then requires its email to be in `STAFF_EMAILS`. That email is
197 who every change is recorded as. See `app/lib/access.ts`.
sudo: Access service tokens listed in STAFF_SERVICE_TOKENS are staff, recorded as <name>@service.g1t.sh198 **Service tokens.** A program (Claude, working without a browser) signs
199 in with an Access service token instead: it sends `CF-Access-Client-Id`
200 and `CF-Access-Client-Secret`, the Access application has a policy with
201 the **Service Auth** action that includes the token, and Access sends
202 sudo a JWT with no email whose `common_name` is the token's client id.
203 sudo lets it in only if that client id is in `STAFF_SERVICE_TOKENS`
204 (`<client id>=<name>`, comma separated, in `wrangler.jsonc`), after the
205 same signature, audience, issuer and expiry checks, and records it as
206 `<name>@service.g1t.sh`: `claude@service.g1t.sh` for
207 `claude-sudo`. Its secret lives in `.credentials/sudo-service-token.json`
208 and is used by `scripts/ops/sudo.mjs`, which sends sudo's own `Origin`
209 with each POST so the same-origin check applies to it as to a browser.
210 To take its access away, remove its entry here, or delete or revoke the
211 token in Zero Trust.
Billing accounts, terms and enterprises; g1t is no longer free2123. **It fails closed.** Until `ACCESS_TEAM_DOMAIN`, `ACCESS_AUD` and
213 `STAFF_EMAILS` are all set, every request gets a 403 saying sudo is not
214 configured.
2154. **Changes** are POSTs only, and only from sudo's own pages (`Origin`, or
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace216 `Referer`, must be `https://sudo.g1t.sh`). Terms, enterprise moves, new
Stripe webhooks, enterprise invoices, and sudo for both217 enterprises, Stripe billing links, invoice emails, invoices and the
218 webhook show a confirmation step first; a credit needs the workspace's
219 slug typed out.
Billing accounts, terms and enterprises; g1t is no longer free2205. **The pages ship no JavaScript.** The content security policy forbids
221 every script and inline style; responses are `no-store`, `noindex` and
222 cannot be framed. The worker has no `workers.dev` address or preview URLs.
223
224## Setting up Access (once, in the Cloudflare dashboard)
225
2261. **Zero Trust → Access → Applications → Add an application → Self-hosted.**
227 - Application name: `sudo`.
228 - Session duration: short, such as 8 hours.
229 - Public hostname: `sudo.g1t.sh` (path empty, so it covers everything).
sudo: Access is on, and everyone at g1t.sh is staff2302. **Add a policy** (`g1t staff`): action *Allow*, include *Emails* → the
231 owner's address, and *Emails ending in* → `g1t.sh` for everyone with a
232 g1t address (the same entries as `STAFF_EMAILS`, where a domain is
233 written `@g1t.sh`). Add more staff here *and* in `STAFF_EMAILS`; either
234 one alone is not enough.
Billing accounts, terms and enterprises; g1t is no longer free2353. Save, then open the application's **Overview** (or *Basic information*)
236 and copy the **Application Audience (AUD) tag**.
2374. Find the **team domain** under **Zero Trust → Settings → Custom pages**
238 (or *Team name and domain*): it looks like `<team>.cloudflareaccess.com`.
2395. Put both into `wrangler.jsonc`:
240
241 ```jsonc
242 "vars": {
243 "ACCESS_TEAM_DOMAIN": "<team>.cloudflareaccess.com",
244 "ACCESS_AUD": "<the AUD tag>",
sudo: Access is on, and everyone at g1t.sh is staff245 "STAFF_EMAILS": "syntaqx@gmail.com, @g1t.sh"
Billing accounts, terms and enterprises; g1t is no longer free246 }
247 ```
248
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace2496. Deploy: `scripts/deploy.sh sudo` (after `billing` and `identity`, whose
250 `admin_*` methods it calls).
Billing accounts, terms and enterprises; g1t is no longer free251
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace252Visit <https://sudo.g1t.sh>: Access asks you to sign in, then the workspaces
Billing accounts, terms and enterprises; g1t is no longer free253list opens. Anyone else gets Access's own refusal; anyone Access lets in who
254is not in `STAFF_EMAILS` gets a 403 from the worker.
255
sudo: WARP sign-in, and comped accounts say Comped256## Signing in through WARP
257
258Staff signed in to the Zero Trust org in the Cloudflare One agent (WARP)
259reach sudo without the login page: the org allows WARP sessions as Access
260sign-ins (8 hours), the sudo app accepts them, and the `g1t staff` policy
261is also on the WARP enrollment app, so staff can enroll their devices.
262The same two checks still apply: the Access policy, and `STAFF_EMAILS`.
263
Billing accounts, terms and enterprises; g1t is no longer free264## Working on it
265
266```sh
267npm run typecheck -w @g1t/sudo
Merge branch 'main' into actions-toolkit-oidc-artifacts268npm test -w @g1t/sudo # JWT verification, forms, money, the workspace join, paging, nav, charts, signals, models
Billing accounts, terms and enterprises; g1t is no longer free269npm run build -w @g1t/sudo
270```
271
272`npm run dev` serves the pages, but every request is refused without a real
273Access token, by design.

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