Skip to content
516 linesCodeBlameRaw
1/**
2 * The Architecture section's diagrams (content/docs/architecture). Each one
3 * shows code that exists on main: when the code changes, change the
4 * diagram in the same pull request, as its page says.
5 */
6import type { Graph, Sequence, Spec } from './engine.ts';
7
8// ── Overview ───────────────────────────────────────────────────────────────
9
10const CORE_X = [20, 206, 392, 578, 764];
11const CORE_Y = [312, 364, 416, 468];
12const core = (id: string, title: string, sub: string, col: number, row: number, tone?: 'accent') => ({
13 id,
14 title,
15 sub,
16 x: CORE_X[col],
17 y: CORE_Y[row],
18 w: 172,
19 h: 44,
20 tone,
21});
22
23const platformMap: Graph = {
24 kind: 'graph',
25 caption: 'g1t at a glance: clients reach four public Workers, which reach the core services through service bindings',
26 width: 960,
27 height: 664,
28 groups: [
29 { x: 4, y: 4, w: 952, h: 92, label: 'Clients', tone: 'default' },
30 { x: 4, y: 140, w: 952, h: 96, label: 'Edge: the public Workers', tone: 'accent' },
31 { id: 'core', x: 4, y: 280, w: 952, h: 244, label: 'Core services: reached through service bindings' },
32 { id: 'platform', x: 4, y: 566, w: 952, h: 94, label: 'Platform: Cloudflare’s primitives, each behind a port' },
33 ],
34 nodes: [
35 { id: 'browser', x: 16, y: 34, w: 168, h: 50, title: 'Browsers and apps', sub: 'site, desktop, phone', tone: 'external' },
36 { id: 'git', x: 206, y: 34, w: 168, h: 50, title: 'git', sub: 'clone, fetch, push', tone: 'external' },
37 { id: 'agents', x: 396, y: 34, w: 168, h: 50, title: 'Coding agents, CLI', sub: 'REST, MCP, OAuth', tone: 'external' },
38 { id: 'runners', x: 586, y: 34, w: 168, h: 50, title: 'Self-hosted runners', sub: 'outbound HTTPS only', tone: 'external' },
39 { id: 'visitors', x: 776, y: 34, w: 168, h: 50, title: 'Visitors to your apps', sub: '*.g1t.page, your domains', tone: 'external' },
40
41 { id: 'web', x: 111, y: 170, w: 168, h: 50, title: 'Site', sub: 'apps/web · g1t.sh', tone: 'accent' },
42 { id: 'status', x: 301, y: 170, w: 168, h: 50, title: 'Status', sub: 'apps/status', tone: 'accent' },
43 { id: 'api', x: 491, y: 170, w: 168, h: 50, title: 'API and MCP', sub: 'apps/api', tone: 'accent' },
44 { id: 'pages', x: 776, y: 170, w: 168, h: 50, title: 'Pages', sub: 'services/pages', tone: 'accent' },
45
46 core('identity', 'identity', 'Rust', 0, 0),
47 core('repos', 'repos', 'Rust', 1, 0),
48 core('work', 'work', 'Rust', 2, 0),
49 core('events', 'events', 'Rust · the bus', 3, 0, 'accent'),
50 core('actions', 'actions', 'Rust', 4, 0),
51 core('runner', 'runner', 'TypeScript · sandboxes', 0, 1),
52 core('deployments', 'deployments', 'TypeScript', 1, 1),
53 core('packages', 'packages', 'Rust', 2, 1),
54 core('security', 'security', 'Rust', 3, 1),
55 core('search', 'search', 'Rust', 4, 1),
56 core('projects', 'projects', 'TypeScript', 0, 2),
57 core('chat', 'chat', 'TypeScript', 1, 2),
58 core('artifacts', 'artifacts', 'TypeScript', 2, 2),
59 core('notify', 'notify', 'TypeScript', 3, 2),
60 core('telemetry', 'telemetry', 'TypeScript', 4, 2),
61 core('billing', 'billing', 'Rust', 0, 3),
62 core('integrations', 'integrations', 'Rust', 1, 3),
63 core('webhooks', 'webhooks', 'Rust', 2, 3),
64 core('og', 'og', 'TypeScript · cards', 3, 3),
65
66 { id: 'd1', x: 13, y: 598, w: 144, h: 50, title: 'D1', sub: 'a database each', shape: 'store' },
67 { id: 'do', x: 171, y: 598, w: 144, h: 50, title: 'Durable Objects', sub: 'live rooms' },
68 { id: 'queues', x: 329, y: 598, w: 144, h: 50, title: 'Queues', sub: 'the event bus' },
69 { id: 'blobs', x: 487, y: 598, w: 144, h: 50, title: 'R2 and KV', sub: 'packs, packages, files', shape: 'store' },
70 { id: 'gitstore', x: 645, y: 598, w: 144, h: 50, title: 'Artifacts', sub: 'git repositories', shape: 'store' },
71 { id: 'containers', x: 803, y: 598, w: 144, h: 50, title: 'Containers', sub: 'one sandbox per job' },
72 ],
73 edges: [
74 { from: 'browser', to: 'web', label: 'HTTPS' },
75 { from: 'git', to: 'web', label: 'smart HTTP' },
76 { from: 'agents', to: 'api', label: 'REST, MCP' },
77 { from: 'runners', to: 'api', label: 'long poll' },
78 { from: 'visitors', to: 'pages' },
79 { from: 'web', to: 'core', toAt: (195 - 480) / 952, label: '`POST /rpc/<method>`', tone: 'accent' },
80 { from: 'api', to: 'core', toAt: (575 - 480) / 952, label: '`POST /rpc/<method>`', tone: 'accent' },
81 { from: 'core', to: 'platform', label: 'bindings' },
82 ],
83};
84
85// ── Requests ───────────────────────────────────────────────────────────────
86
87const pageRequest: Sequence = {
88 kind: 'sequence',
89 caption: 'A signed-in page: the viewer is resolved once, then every loader calls its service at the same time',
90 actors: [
91 { id: 'browser', title: 'Browser', tone: 'external' },
92 { id: 'web', title: 'Site', sub: 'apps/web', tone: 'accent' },
93 { id: 'identity', title: 'identity', sub: 'services/identity' },
94 { id: 'work', title: 'work', sub: 'services/work' },
95 { id: 'repos', title: 'repos', sub: 'services/repos' },
96 ],
97 steps: [
98 { from: 'browser', to: 'web', label: '`GET /acme/app/pull/12`, `g1t_session`' },
99 { from: 'web', to: 'identity', label: '`user_for_session`' },
100 { from: 'identity', to: 'web', label: 'the user: workspaces, roles, grants', reply: true },
101 { phase: 'Loaders start together' },
102 { from: 'web', to: 'work', label: '`POST /rpc/<method>` with the viewer' },
103 { from: 'web', to: 'repos', label: '`POST /rpc/<method>` with the viewer' },
104 { note: 'each decides with `access::can`, without asking identity', over: ['work', 'repos'] },
105 { from: 'work', to: 'web', label: '`{ ok, value }`, its D1 time', reply: true },
106 { from: 'repos', to: 'web', label: '`{ ok, value }`, its D1 time', reply: true },
107 { from: 'web', to: 'browser', label: 'HTML, with `Server-Timing`', reply: true },
108 ],
109};
110
111const apiRequest: Sequence = {
112 kind: 'sequence',
113 caption: 'An MCP tool call: the same token check, scopes, and service calls as the REST API',
114 actors: [
115 { id: 'agent', title: 'Coding agent', sub: 'MCP client', tone: 'external' },
116 { id: 'api', title: 'API and MCP', sub: 'apps/api', tone: 'accent' },
117 { id: 'identity', title: 'identity', sub: 'services/identity' },
118 { id: 'work', title: 'work', sub: 'services/work' },
119 ],
120 steps: [
121 { from: 'agent', to: 'api', label: '`tools/call` `pull_request`, `Bearer g1t_…`' },
122 { from: 'api', to: 'api', label: 'rate limit by token hash' },
123 { from: 'api', to: 'identity', label: '`user_for_access_token`' },
124 { from: 'identity', to: 'api', label: 'the user, the token’s scopes', reply: true },
125 { from: 'api', to: 'api', label: 'the gate: do the scopes allow it?' },
126 { from: 'api', to: 'work', label: '`POST /rpc/<method>` with the viewer' },
127 { from: 'work', to: 'api', label: '`{ ok, value }` in camelCase', reply: true },
128 { from: 'api', to: 'agent', label: 'JSON-RPC result, in snake_case', reply: true },
129 ],
130};
131
132// ── Git ────────────────────────────────────────────────────────────────────
133
134const pushPath: Sequence = {
135 kind: 'sequence',
136 caption: 'A push: checked before anything is stored, answered as soon as the refs move, announced afterwards',
137 width: 900,
138 actors: [
139 { id: 'git', title: 'git', sub: 'your machine', tone: 'external' },
140 { id: 'web', title: 'Site', sub: 'g1t.sh', tone: 'accent' },
141 { id: 'repos', title: 'repos', sub: 'services/repos', tone: 'accent' },
142 { id: 'identity', title: 'identity' },
143 { id: 'work', title: 'work' },
144 { id: 'store', title: 'Artifacts', sub: 'the git store', tone: 'store' },
145 { id: 'events', title: 'events' },
146 ],
147 steps: [
148 { from: 'git', to: 'web', label: '`POST …/git-receive-pack`' },
149 { from: 'web', to: 'repos', label: 'passed on whole' },
150 { from: 'repos', to: 'identity', label: '`user_for_git_credentials`' },
151 { from: 'identity', to: 'repos', label: 'the user, or no one', reply: true },
152 { from: 'repos', to: 'work', label: '`ref_rules`' },
153 { phase: 'Checks read the pack once, side by side' },
154 { from: 'repos', to: 'repos', label: 'workflow gate · rules · sizes · push protection' },
155 { from: 'repos', to: 'store', label: 'the pack, with a store credential' },
156 { from: 'store', to: 'repos', label: 'report-status', reply: true },
157 { from: 'repos', to: 'repos', label: 'bump `refs_version`' },
158 { from: 'repos', to: 'git', label: 'the report, with `Server-Timing`', reply: true },
159 { phase: 'After the answer, in waitUntil' },
160 { from: 'repos', to: 'store', label: '`log`: did each ref move?' },
161 { from: 'repos', to: 'events', label: 'publish `git.push`', async: true },
162 ],
163};
164
165const clonePath: Graph = {
166 kind: 'graph',
167 caption: 'A clone or fetch: answered from the nearest cache that can, and from the git store only when none can',
168 width: 930,
169 height: 316,
170 nodes: [
171 { id: 'git', x: 10, y: 140, w: 130, title: 'git', sub: 'clone, fetch', tone: 'external' },
172 { id: 'web', x: 200, y: 140, w: 130, title: 'Site', sub: 'g1t.sh' },
173 { id: 'repos', x: 390, y: 140, w: 140, title: 'repos', sub: 'upload-pack', tone: 'accent' },
174 { id: 'colo', x: 740, y: 12, w: 180, title: 'Colo cache', sub: 'refs 60 s, objects forever' },
175 { id: 'kv', x: 740, y: 92, w: 180, title: 'KV, sealed', sub: 'refs, store credentials', shape: 'store' },
176 { id: 'packs', x: 740, y: 172, w: 180, title: 'R2 `g1t-git-packs`', sub: 'fresh-clone packs', shape: 'store' },
177 { id: 'store', x: 740, y: 252, w: 180, title: 'Artifacts', sub: 'the git store', shape: 'store' },
178 ],
179 edges: [
180 { from: 'git', to: 'web' },
181 { from: 'web', to: 'repos', label: '`REPOS.fetch`' },
182 ...([
183 ['colo', 'ref listing, this colo', 38],
184 ['kv', 'ref listing, shared', 121],
185 ['packs', 'a fresh clone', 201],
186 ['store', 'everything else', 281],
187 ] as const).map(([to, label, y]) => ({
188 from: 'repos',
189 to,
190 fromSide: 'right' as const,
191 toSide: 'left' as const,
192 bend: 570,
193 label,
194 labelAt: [655, y] as [number, number],
195 tone: to === 'store' ? ('accent' as const) : undefined,
196 })),
197 ],
198};
199
200// ── Events ─────────────────────────────────────────────────────────────────
201
202const SUBSCRIBERS = ['work', 'runner', 'actions', 'webhooks', 'security', 'search', 'deployments', 'projects', 'repos', 'packages', 'billing', 'integrations', 'artifacts'];
203
204const eventFanout: Graph = {
205 kind: 'graph',
206 caption: 'One publish, written once to the log, then copied to a queue for each service that subscribes',
207 width: 960,
208 height: 500,
209 groups: [
210 { x: 4, y: 4, w: 176, h: 400, label: 'Publishers' },
211 { x: 778, y: 4, w: 178, h: 492, label: 'Subscriber queues', tone: 'accent' },
212 ],
213 nodes: [
214 { id: 'p-repos', x: 16, y: 40, w: 152, h: 46, title: 'repos', sub: '`git.push`, `repo.*`' },
215 { id: 'p-work', x: 16, y: 110, w: 152, h: 46, title: 'work', sub: '`pull.*`, `issue.*`' },
216 { id: 'p-actions', x: 16, y: 180, w: 152, h: 46, title: 'actions', sub: '`workflow.*`' },
217 { id: 'p-identity', x: 16, y: 250, w: 152, h: 46, title: 'identity', sub: '`workspace.*`' },
218 { id: 'p-more', x: 16, y: 320, w: 152, h: 46, title: 'and the rest', sub: 'any service' },
219 { id: 'publish', x: 222, y: 180, w: 150, h: 46, title: 'events', sub: '`publish` → `sendBatch`', tone: 'accent' },
220 { id: 'bus', x: 410, y: 180, w: 150, h: 46, title: 'Queue `g1t-events`', sub: 'batches of 100' },
221 { id: 'deliver', x: 600, y: 180, w: 150, h: 46, title: 'events', sub: '`deliver`', tone: 'accent' },
222 { id: 'log', x: 600, y: 40, w: 150, h: 50, title: 'D1 `g1t-events`', sub: 'the log, by `seq`', shape: 'store' },
223 { id: 'notify', x: 600, y: 320, w: 150, h: 46, title: 'notify', sub: 'live pages, inbox' },
224 { id: 'dlq', x: 410, y: 320, w: 150, h: 46, title: 'Dead letters', sub: '`g1t-events-dlq`', tone: 'warn' },
225 ...SUBSCRIBERS.map((name, i) => ({ id: `s-${name}`, x: 790, y: 40 + i * 35, w: 154, h: 29, title: name })),
226 ],
227 edges: [
228 ...['p-repos', 'p-work', 'p-actions', 'p-identity', 'p-more'].map((from) => ({ from, to: 'publish', fromSide: 'right' as const, toSide: 'left' as const, bend: 196 })),
229 { from: 'publish', to: 'bus', async: true },
230 { from: 'bus', to: 'deliver', async: true },
231 { from: 'deliver', to: 'log', label: '`INSERT OR IGNORE`' },
232 { from: 'deliver', to: 'notify', label: '`repo_changed`' },
233 { from: 'bus', to: 'dlq', label: 'after 3 tries', async: true, tone: 'warn' },
234 ...SUBSCRIBERS.map((name) => ({ from: 'deliver', to: `s-${name}`, fromSide: 'right' as const, toSide: 'left' as const, bend: 770, async: true })),
235 ],
236};
237
238const followEvents: Sequence = {
239 kind: 'sequence',
240 caption: 'Following a repository’s events: a long poll that resumes from the last event it saw',
241 width: 680,
242 actors: [
243 { id: 'cli', title: 'g1t CLI', sub: '`work events --follow`', tone: 'external' },
244 { id: 'api', title: 'API', sub: 'apps/api', tone: 'accent' },
245 { id: 'events', title: 'events', sub: 'services/events' },
246 ],
247 steps: [
248 { from: 'cli', to: 'api', label: '`list_events` `after=evt_…`, `wait=25`' },
249 { from: 'api', to: 'events', label: '`list` after the cursor' },
250 { from: 'events', to: 'api', label: 'nothing yet', reply: true },
251 { note: 'asks again about once a second, for up to 25 s', over: ['api', 'events'] },
252 { from: 'api', to: 'events', label: '`list` after the cursor' },
253 { from: 'events', to: 'api', label: 'events, in `seq` order', reply: true },
254 { from: 'api', to: 'cli', label: 'events, oldest first', reply: true },
255 { from: 'cli', to: 'cli', label: 'ask again after the last id' },
256 ],
257};
258
259// ── Work ───────────────────────────────────────────────────────────────────
260
261const mergeQueue: Graph = {
262 kind: 'graph',
263 caption: 'The merge queue: each entry is tested on top of the entries ahead of it, all at once, and lands in order',
264 width: 920,
265 height: 318,
266 nodes: [
267 { id: 'main', x: 10, y: 40, w: 140, title: '`main`', sub: 'where it stands', tone: 'store' },
268 { id: 'c1', x: 200, y: 40, w: 160, title: '`g1t-queue/…41`', sub: 'main + #41', tone: 'accent' },
269 { id: 'c2', x: 380, y: 40, w: 160, title: '`g1t-queue/…42`', sub: '+ #41 + #42', tone: 'accent' },
270 { id: 'c3', x: 560, y: 40, w: 160, title: '`g1t-queue/…43`', sub: '+ #41 + #42 + #43', tone: 'accent' },
271 { id: 'c4', x: 740, y: 40, w: 170, title: '`g1t-queue/…44`', sub: '+ #41 + #42 + #44', tone: 'accent' },
272 { id: 'k1', x: 200, y: 150, w: 160, h: 44, title: 'required checks pass', tone: 'success' },
273 { id: 'k2', x: 380, y: 150, w: 160, h: 44, title: 'required checks pass', tone: 'success' },
274 { id: 'k3', x: 560, y: 150, w: 160, h: 44, title: 'a required check fails', tone: 'warn' },
275 { id: 'k4', x: 740, y: 150, w: 170, h: 44, title: 'required checks pass', tone: 'success' },
276 { id: 'l1', x: 200, y: 254, w: 160, h: 44, title: '#41 lands first' },
277 { id: 'l2', x: 380, y: 254, w: 160, h: 44, title: '#42 lands next' },
278 { id: 'l3', x: 560, y: 254, w: 160, h: 44, title: '#43 leaves the queue', tone: 'warn' },
279 { id: 'l4', x: 740, y: 254, w: 170, h: 44, title: '#44 lands third' },
280 ],
281 edges: [
282 { from: 'main', to: 'c1' },
283 { from: 'c1', to: 'c2' },
284 { from: 'c2', to: 'c3' },
285 { from: 'c2', to: 'c4', fromSide: 'top', toSide: 'top', bend: 14, async: true, label: 'retested without #43', labelAt: [640, 14] },
286 { from: 'c1', to: 'k1', label: '`merge_group`' },
287 { from: 'c2', to: 'k2', label: '`merge_group`' },
288 { from: 'c3', to: 'k3', label: '`merge_group`', tone: 'warn' },
289 { from: 'c4', to: 'k4', label: '`merge_group`' },
290 { from: 'k1', to: 'l1', tone: 'success' },
291 { from: 'k2', to: 'l2', tone: 'success' },
292 { from: 'k3', to: 'l3', tone: 'warn' },
293 { from: 'k4', to: 'l4', tone: 'success' },
294 ],
295};
296
297const coordination: Sequence = {
298 kind: 'sequence',
299 caption: 'Two agents on one repository: sessions and claims are advisory, and each learns of the other from events',
300 width: 860,
301 actors: [
302 { id: 'a', title: 'Agent A', sub: 'its own harness', tone: 'external' },
303 { id: 'api', title: 'API and MCP', sub: 'apps/api' },
304 { id: 'work', title: 'work', sub: 'services/work', tone: 'accent' },
305 { id: 'events', title: 'events' },
306 { id: 'b', title: 'Agent B', sub: '`--follow`', tone: 'external' },
307 ],
308 steps: [
309 { from: 'a', to: 'api', label: '`session` `start`, `harness_session_id`' },
310 { from: 'api', to: 'work', label: 'start, idempotent on that id' },
311 { from: 'a', to: 'api', label: '`issue` `work` #12' },
312 { from: 'api', to: 'work', label: 'issue, claims, sessions, pulls, blockers' },
313 { from: 'a', to: 'api', label: '`issue` `claim` #12' },
314 { from: 'api', to: 'work', label: 'claim: 30 min unless renewed' },
315 { from: 'work', to: 'events', label: '`work.claimed`', async: true },
316 { from: 'api', to: 'b', label: 'its `list_events` long poll returns `work.claimed`', reply: true },
317 { note: 'B may still claim #12; it is told about A, and nothing is refused', over: ['work', 'b'] },
318 { phase: 'While A works' },
319 { from: 'a', to: 'api', label: 'heartbeat, entries: renew the claim' },
320 { note: 'quiet for 15 min: idle; for 2 h: ended; noticed on the next read', over: ['api', 'work'] },
321 ],
322};
323
324// ── Compute ────────────────────────────────────────────────────────────────
325
326const actionsJob: Sequence = {
327 kind: 'sequence',
328 caption: 'A workflow job on g1t’s runners, from the push that starts it to the status on the commit',
329 width: 900,
330 actors: [
331 { id: 'events', title: 'events' },
332 { id: 'actions', title: 'actions', sub: 'services/actions', tone: 'accent' },
333 { id: 'runner', title: 'runner', sub: 'services/runner' },
334 { id: 'sandbox', title: 'Sandbox', sub: '`g1t-runner`', tone: 'success' },
335 { id: 'api', title: 'API', sub: 'apps/api' },
336 { id: 'work', title: 'work', sub: 'services/work' },
337 ],
338 steps: [
339 { from: 'events', to: 'actions', label: '`git.push` on `g1t-events-actions`', async: true },
340 { from: 'actions', to: 'actions', label: 'match workflows, plan the jobs' },
341 { from: 'actions', to: 'work', label: '`set_commit_status` pending' },
342 { from: 'actions', to: 'runner', label: '`start_actions_job`' },
343 { from: 'runner', to: 'sandbox', label: 'a Container for this job alone' },
344 { phase: 'The job runs' },
345 { from: 'sandbox', to: 'api', label: '`POST /actions/jobs/{job}`: steps, logs' },
346 { from: 'api', to: 'actions', label: '`job_report`' },
347 { from: 'api', to: 'sandbox', label: 'go on, or cancel', reply: true },
348 { note: 'HTTP(S) leaves only through the runner’s egress handler', over: ['runner', 'sandbox'] },
349 { phase: 'The job ends' },
350 { from: 'actions', to: 'work', label: '`set_commit_status` success or failure' },
351 { from: 'actions', to: 'events', label: '`workflow.completed`', async: true },
352 ],
353};
354
355const sandboxes: Graph = {
356 kind: 'graph',
357 caption: 'Where a job runs: in a Container on g1t’s runners, or inside three walls on a machine of yours',
358 width: 940,
359 height: 330,
360 groups: [
361 { x: 4, y: 4, w: 452, h: 322, label: 'g1t’s runners: Cloudflare Containers', tone: 'success' },
362 { x: 24, y: 40, w: 230, h: 190, label: 'one sandbox per job' },
363 { x: 474, y: 4, w: 462, h: 322, label: 'Your machine: three walls', tone: 'success' },
364 { x: 490, y: 40, w: 300, h: 274, label: 'VM: WSL2, vfkit or QEMU' },
365 { id: 'sysbox', x: 506, y: 76, w: 268, h: 222, label: 'Sysbox container: `g1t-runner`' },
366 ],
367 nodes: [
368 { id: 'h-runner', x: 44, y: 78, w: 190, title: '`g1t-runner`', sub: 'does one job, exits' },
369 { id: 'h-docker', x: 44, y: 156, w: 190, title: 'its own dockerd', sub: 'when `DOCKER=on`' },
370 { id: 'egress', x: 290, y: 78, w: 150, title: 'egress handler', sub: 'all HTTP(S) leaves here', tone: 'accent' },
371 { id: 'hosts', x: 290, y: 250, w: 150, title: 'allowed hosts', sub: 'registries, your APIs', tone: 'external' },
372 { id: 'job', x: 526, y: 116, w: 228, title: 'job container', sub: 'new for every job' },
373 { id: 'dockerd', x: 526, y: 210, w: 228, title: 'its own dockerd', sub: 'never the host’s `docker.sock`' },
374 { id: 'api', x: 802, y: 160, w: 130, title: 'api.g1t.sh', sub: 'long poll, outbound', tone: 'external' },
375 ],
376 edges: [
377 { from: 'h-runner', to: 'egress' },
378 { from: 'egress', to: 'hosts', label: '`g1tsec_…` → the secret' },
379 { from: 'sysbox', to: 'api', fromSide: 'right', toSide: 'left' },
380 ],
381};
382
383const deploymentPath: Graph = {
384 kind: 'graph',
385 caption: 'Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname',
386 width: 940,
387 height: 330,
388 nodes: [
389 { id: 'repos', x: 10, y: 30, w: 160, title: 'repos', sub: 'a push to a branch' },
390 { id: 'deployments', x: 260, y: 30, w: 170, title: 'deployments', sub: 'services/deployments', tone: 'accent' },
391 { id: 'build', x: 520, y: 30, w: 180, title: 'build sandbox', sub: '`g1t-runner`, `MODE=deploy`', tone: 'success' },
392 { id: 'kv', x: 260, y: 150, w: 170, title: 'KV `g1t-domains`', sub: 'hostname → app', shape: 'store' },
393 { id: 'wfp', x: 520, y: 150, w: 180, title: 'dispatch namespace', sub: '`g1t-deployments`', tone: 'accent' },
394 { id: 'telemetry', x: 760, y: 150, w: 170, title: 'telemetry', sub: 'tail events, per project' },
395 { id: 'visitors', x: 10, y: 268, w: 160, title: 'Visitors', sub: '`*.g1t.page`, your domain', tone: 'external' },
396 { id: 'pages', x: 260, y: 268, w: 170, title: 'pages', sub: 'services/pages', tone: 'accent' },
397 { id: 'otel', x: 760, y: 268, w: 170, title: 'Your software', sub: 'OTLP to otel.g1t.sh', tone: 'external' },
398 ],
399 edges: [
400 { from: 'repos', to: 'deployments', label: '`git.push`', async: true },
401 { from: 'deployments', to: 'build', label: '`start_deploy`' },
402 { from: 'deployments', to: 'wfp', fromSide: 'bottom', toSide: 'top', fromAt: 0.4, toAt: -0.4, bend: 116, label: 'the built Worker' },
403 { from: 'deployments', to: 'kv', fromAt: -0.2, toAt: -0.2, label: 'domains' },
404 { from: 'visitors', to: 'pages' },
405 { from: 'pages', to: 'kv', label: 'which app?' },
406 { from: 'pages', to: 'wfp', fromSide: 'right', toSide: 'bottom', label: '`APPS.get(app)`' },
407 { from: 'wfp', to: 'telemetry', async: true },
408 { from: 'otel', to: 'telemetry', label: 'OTLP' },
409 ],
410};
411
412// ── Data and access ────────────────────────────────────────────────────────
413
414/** Each port: its adapter on g1t.sh, the port, and its adapter when self-hosted, each with a second line. */
415const PORT_ROWS: [string, string, string, string, string, string][] = [
416 ['Cloudflare Artifacts', 'namespaces, sharded', '`GitStore`', 'services/repos', 'gitstore', 'bare repositories, `git http-backend`'],
417 ['R2', 'a bucket per use', '`BlobStore`', 'crates/blobstore', 'S3', 'RustFS, MinIO, Ceph, AWS'],
418 ['Email Sending', 'from g1t.sh', '`EMAIL`', 'a `send_email` binding', 'the mail Worker', 'Mailpit, then your SMTP'],
419 ['Containers', 'services/runner', '`RUNNER`', 'a service binding', 'the off Worker', 'answers that it is off'],
420 ['D1, KV, Queues, DOs', 'Cloudflare’s', 'Workers bindings', 'unchanged code', 'workerd', 'SQLite files on a volume'],
421];
422
423const portsAndAdapters: Graph = {
424 kind: 'graph',
425 caption: 'Ports and adapters: the same service code, with a Cloudflare adapter on g1t.sh and an open one when you run it yourself',
426 width: 940,
427 height: 400,
428 groups: [
429 { x: 6, y: 4, w: 262, h: 392, label: 'On g1t.sh' },
430 { x: 662, y: 4, w: 272, h: 392, label: 'Self-hosted, in Docker Compose', tone: 'success' },
431 ],
432 nodes: PORT_ROWS.flatMap(([hosted, hostedSub, port, portSub, self, selfSub], i) => {
433 const y = 40 + i * 72;
434 return [
435 { id: `h${i}`, x: 22, y, w: 230, h: 50, title: hosted, sub: hostedSub },
436 { id: `p${i}`, x: 352, y, w: 230, h: 50, title: port, sub: portSub, tone: 'accent' as const },
437 { id: `s${i}`, x: 678, y, w: 240, h: 50, title: self, sub: selfSub, tone: 'success' as const },
438 ];
439 }),
440 edges: PORT_ROWS.flatMap((_, i) => [
441 { from: `p${i}`, to: `h${i}` },
442 { from: `p${i}`, to: `s${i}` },
443 ]),
444};
445
446const identityFlow: Graph = {
447 kind: 'graph',
448 caption: 'Every credential becomes the same viewer, resolved once per request; each service decides for itself',
449 width: 940,
450 height: 340,
451 groups: [{ x: 4, y: 4, w: 226, h: 332, label: 'Credentials' }],
452 nodes: [
453 { id: 'cookie', x: 18, y: 32, w: 198, h: 50, title: 'Session cookie', sub: '`g1t_session`, the site', tone: 'external' },
454 { id: 'pat', x: 18, y: 92, w: 198, h: 50, title: 'Access token', sub: '`g1t_…`, scoped', tone: 'external' },
455 { id: 'oauth', x: 18, y: 152, w: 198, h: 50, title: 'OAuth token', sub: 'MCP clients, apps', tone: 'external' },
456 { id: 'job', x: 18, y: 212, w: 198, h: 50, title: 'Job or run token', sub: 'one workflow job', tone: 'external' },
457 { id: 'gitpw', x: 18, y: 272, w: 198, h: 50, title: 'git password', sub: 'refused with 2FA on', tone: 'external' },
458 { id: 'identity', x: 286, y: 146, w: 160, h: 62, title: 'identity', sub: '`user_for_…`', tone: 'accent' },
459 { id: 'viewer', x: 556, y: 146, w: 170, h: 62, title: 'the viewer', sub: 'kind, workspaces, grants' },
460 { id: 'services', x: 836, y: 146, w: 98, h: 62, title: 'services', sub: '`access::can`', tone: 'accent' },
461 ],
462 edges: [
463 ...['cookie', 'pat', 'oauth', 'job', 'gitpw'].map((from) => ({ from, to: 'identity', fromSide: 'right' as const, toSide: 'left' as const, bend: 256 })),
464 { from: 'identity', to: 'viewer', label: 'once per request' },
465 { from: 'viewer', to: 'services', label: 'in every RPC body' },
466 ],
467};
468
469// ── Shipping ───────────────────────────────────────────────────────────────
470
471const STAGES: [string, string, string][] = [
472 ['push', 'a push to `main`', ''],
473 ['check', 'check, plan', 'manifest, tests'],
474 ['migrate', 'migrations', 'every pending one'],
475 ['core', 'core', 'the services'],
476 ['edge', 'edge', 'api, pages, status'],
477 ['front', 'front', 'site, sudo, docs'],
478 ['smoke', 'smoke', 'sign-in works'],
479];
480
481const deployStages: Graph = {
482 kind: 'graph',
483 caption: 'How g1t ships itself: each stage starts only when the one before it succeeded',
484 width: 940,
485 height: 140,
486 groups: [{ x: 140, y: 4, w: 796, h: 132, label: '`.g1t/workflows/deploy.yml`, on g1t Actions', tone: 'accent' }],
487 nodes: STAGES.map(([id, title, sub], i) => ({
488 id,
489 x: 10 + i * 133,
490 y: 56,
491 w: 118,
492 h: 50,
493 title,
494 sub: sub || undefined,
495 tone: id === 'push' ? ('external' as const) : id === 'smoke' ? ('success' as const) : undefined,
496 })),
497 edges: STAGES.slice(1).map(([id], i) => ({ from: STAGES[i][0], to: id })),
498};
499
500export const ARCHITECTURE: Record<string, Spec> = {
501 'platform-map': platformMap,
502 'page-request': pageRequest,
503 'api-request': apiRequest,
504 'push-path': pushPath,
505 'clone-path': clonePath,
506 'event-fanout': eventFanout,
507 'follow-events': followEvents,
508 'merge-queue': mergeQueue,
509 coordination,
510 'actions-job': actionsJob,
511 sandboxes,
512 'deployment-path': deploymentPath,
513 'ports-and-adapters': portsAndAdapters,
514 'identity-flow': identityFlow,
515 'deploy-stages': deployStages,
516};