| 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 | */ |
| 6 | import type { Graph, Sequence, Spec } from './engine.ts'; |
| 7 | |
| 8 | // ── Overview ─────────────────────────────────────────────────────────────── |
| 9 | |
| 10 | const CORE_X = [20, 206, 392, 578, 764]; |
| 11 | const CORE_Y = [312, 364, 416, 468]; |
| 12 | const 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 | |
| 23 | const 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 | |
| 87 | const 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 | |
| 111 | const 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 | |
| 134 | const 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 | |
| 165 | const 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 | |
| 202 | const SUBSCRIBERS = ['work', 'runner', 'actions', 'webhooks', 'security', 'search', 'deployments', 'projects', 'repos', 'packages', 'billing', 'integrations', 'artifacts']; |
| 203 | |
| 204 | const 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 | |
| 238 | const 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 | |
| 261 | const 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 | |
| 297 | const 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 | |
| 326 | const 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 | |
| 355 | const 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 | |
| 383 | const 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. */ |
| 415 | const 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 | |
| 423 | const 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 | |
| 446 | const 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 | |
| 471 | const 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 | |
| 481 | const 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 | |
| 500 | export 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 | }; |