| 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 | [ |
| 184 | ["colo", "ref listing, this colo", 38], |
| 185 | ["kv", "ref listing, shared", 121], |
| 186 | ["packs", "a fresh clone", 201], |
| 187 | ["store", "everything else", 281], |
| 188 | ] as const |
| 189 | ).map(([to, label, y]) => ({ |
| 190 | from: "repos", |
| 191 | to, |
| 192 | fromSide: "right" as const, |
| 193 | toSide: "left" as const, |
| 194 | bend: 570, |
| 195 | label, |
| 196 | labelAt: [655, y] as [number, number], |
| 197 | tone: to === "store" ? ("accent" as const) : undefined, |
| 198 | })), |
| 199 | ], |
| 200 | }; |
| 201 | |
| 202 | // ── Events ───────────────────────────────────────────────────────────────── |
| 203 | |
| 204 | const SUBSCRIBERS = [ |
| 205 | "work", |
| 206 | "runner", |
| 207 | "actions", |
| 208 | "webhooks", |
| 209 | "security", |
| 210 | "search", |
| 211 | "deployments", |
| 212 | "projects", |
| 213 | "repos", |
| 214 | "packages", |
| 215 | "billing", |
| 216 | "integrations", |
| 217 | "artifacts", |
| 218 | ]; |
| 219 | |
| 220 | const eventFanout: Graph = { |
| 221 | kind: "graph", |
| 222 | caption: "One publish, written once to the log, then copied to a queue for each service that subscribes", |
| 223 | width: 960, |
| 224 | height: 500, |
| 225 | groups: [ |
| 226 | { x: 4, y: 4, w: 176, h: 400, label: "Publishers" }, |
| 227 | { x: 778, y: 4, w: 178, h: 492, label: "Subscriber queues", tone: "accent" }, |
| 228 | ], |
| 229 | nodes: [ |
| 230 | { id: "p-repos", x: 16, y: 40, w: 152, h: 46, title: "repos", sub: "`git.push`, `repo.*`" }, |
| 231 | { id: "p-work", x: 16, y: 110, w: 152, h: 46, title: "work", sub: "`pull.*`, `work_item.*`" }, |
| 232 | { id: "p-actions", x: 16, y: 180, w: 152, h: 46, title: "actions", sub: "`workflow.*`" }, |
| 233 | { id: "p-identity", x: 16, y: 250, w: 152, h: 46, title: "identity", sub: "`workspace.*`" }, |
| 234 | { id: "p-more", x: 16, y: 320, w: 152, h: 46, title: "and the rest", sub: "any service" }, |
| 235 | { id: "publish", x: 222, y: 180, w: 150, h: 46, title: "events", sub: "`publish` → `sendBatch`", tone: "accent" }, |
| 236 | { id: "bus", x: 410, y: 180, w: 150, h: 46, title: "Queue `g1t-events`", sub: "batches of 100" }, |
| 237 | { id: "deliver", x: 600, y: 180, w: 150, h: 46, title: "events", sub: "`deliver`", tone: "accent" }, |
| 238 | { id: "log", x: 600, y: 40, w: 150, h: 50, title: "D1 `g1t-events`", sub: "the log, by `seq`", shape: "store" }, |
| 239 | { id: "notify", x: 600, y: 320, w: 150, h: 46, title: "notify", sub: "live pages, inbox" }, |
| 240 | { id: "dlq", x: 410, y: 320, w: 150, h: 46, title: "Dead letters", sub: "`g1t-events-dlq`", tone: "warn" }, |
| 241 | ...SUBSCRIBERS.map((name, i) => ({ id: `s-${name}`, x: 790, y: 40 + i * 35, w: 154, h: 29, title: name })), |
| 242 | ], |
| 243 | edges: [ |
| 244 | ...["p-repos", "p-work", "p-actions", "p-identity", "p-more"].map((from) => ({ |
| 245 | from, |
| 246 | to: "publish", |
| 247 | fromSide: "right" as const, |
| 248 | toSide: "left" as const, |
| 249 | bend: 196, |
| 250 | })), |
| 251 | { from: "publish", to: "bus", async: true }, |
| 252 | { from: "bus", to: "deliver", async: true }, |
| 253 | { from: "deliver", to: "log", label: "`INSERT OR IGNORE`" }, |
| 254 | { from: "deliver", to: "notify", label: "`repo_changed`" }, |
| 255 | { from: "bus", to: "dlq", label: "after 3 tries", async: true, tone: "warn" }, |
| 256 | ...SUBSCRIBERS.map((name) => ({ from: "deliver", to: `s-${name}`, fromSide: "right" as const, toSide: "left" as const, bend: 770, async: true })), |
| 257 | ], |
| 258 | }; |
| 259 | |
| 260 | const followEvents: Sequence = { |
| 261 | kind: "sequence", |
| 262 | caption: "Following a repository’s events: a long poll that resumes from the last event it saw", |
| 263 | width: 680, |
| 264 | actors: [ |
| 265 | { id: "cli", title: "g1t CLI", sub: "`work events --follow`", tone: "external" }, |
| 266 | { id: "api", title: "API", sub: "apps/api", tone: "accent" }, |
| 267 | { id: "events", title: "events", sub: "services/events" }, |
| 268 | ], |
| 269 | steps: [ |
| 270 | { from: "cli", to: "api", label: "`list_events` `after=evt_…`, `wait=25`" }, |
| 271 | { from: "api", to: "events", label: "`list` after the cursor" }, |
| 272 | { from: "events", to: "api", label: "nothing yet", reply: true }, |
| 273 | { note: "asks again about once a second, for up to 25 s", over: ["api", "events"] }, |
| 274 | { from: "api", to: "events", label: "`list` after the cursor" }, |
| 275 | { from: "events", to: "api", label: "events, in `seq` order", reply: true }, |
| 276 | { from: "api", to: "cli", label: "events, oldest first", reply: true }, |
| 277 | { from: "cli", to: "cli", label: "ask again after the last id" }, |
| 278 | ], |
| 279 | }; |
| 280 | |
| 281 | // ── Work ─────────────────────────────────────────────────────────────────── |
| 282 | |
| 283 | const mergeQueue: Graph = { |
| 284 | kind: "graph", |
| 285 | caption: "The merge queue: each entry is tested on top of the entries ahead of it, all at once, and lands in order", |
| 286 | width: 920, |
| 287 | height: 318, |
| 288 | nodes: [ |
| 289 | { id: "main", x: 10, y: 40, w: 140, title: "`main`", sub: "where it stands", tone: "store" }, |
| 290 | { id: "c1", x: 200, y: 40, w: 160, title: "`g1t-queue/…41`", sub: "main + #41", tone: "accent" }, |
| 291 | { id: "c2", x: 380, y: 40, w: 160, title: "`g1t-queue/…42`", sub: "+ #41 + #42", tone: "accent" }, |
| 292 | { id: "c3", x: 560, y: 40, w: 160, title: "`g1t-queue/…43`", sub: "+ #41 + #42 + #43", tone: "accent" }, |
| 293 | { id: "c4", x: 740, y: 40, w: 170, title: "`g1t-queue/…44`", sub: "+ #41 + #42 + #44", tone: "accent" }, |
| 294 | { id: "k1", x: 200, y: 150, w: 160, h: 44, title: "required checks pass", tone: "success" }, |
| 295 | { id: "k2", x: 380, y: 150, w: 160, h: 44, title: "required checks pass", tone: "success" }, |
| 296 | { id: "k3", x: 560, y: 150, w: 160, h: 44, title: "a required check fails", tone: "warn" }, |
| 297 | { id: "k4", x: 740, y: 150, w: 170, h: 44, title: "required checks pass", tone: "success" }, |
| 298 | { id: "l1", x: 200, y: 254, w: 160, h: 44, title: "#41 lands first" }, |
| 299 | { id: "l2", x: 380, y: 254, w: 160, h: 44, title: "#42 lands next" }, |
| 300 | { id: "l3", x: 560, y: 254, w: 160, h: 44, title: "#43 leaves the queue", tone: "warn" }, |
| 301 | { id: "l4", x: 740, y: 254, w: 170, h: 44, title: "#44 lands third" }, |
| 302 | ], |
| 303 | edges: [ |
| 304 | { from: "main", to: "c1" }, |
| 305 | { from: "c1", to: "c2" }, |
| 306 | { from: "c2", to: "c3" }, |
| 307 | { from: "c2", to: "c4", fromSide: "top", toSide: "top", bend: 14, async: true, label: "retested without #43", labelAt: [640, 14] }, |
| 308 | { from: "c1", to: "k1", label: "`merge_group`" }, |
| 309 | { from: "c2", to: "k2", label: "`merge_group`" }, |
| 310 | { from: "c3", to: "k3", label: "`merge_group`", tone: "warn" }, |
| 311 | { from: "c4", to: "k4", label: "`merge_group`" }, |
| 312 | { from: "k1", to: "l1", tone: "success" }, |
| 313 | { from: "k2", to: "l2", tone: "success" }, |
| 314 | { from: "k3", to: "l3", tone: "warn" }, |
| 315 | { from: "k4", to: "l4", tone: "success" }, |
| 316 | ], |
| 317 | }; |
| 318 | |
| 319 | const coordination: Sequence = { |
| 320 | kind: "sequence", |
| 321 | caption: "Two agents on one repository: sessions and claims are advisory, and each learns of the other from events", |
| 322 | width: 860, |
| 323 | actors: [ |
| 324 | { id: "a", title: "Agent A", sub: "its own harness", tone: "external" }, |
| 325 | { id: "api", title: "API and MCP", sub: "apps/api" }, |
| 326 | { id: "work", title: "work", sub: "services/work", tone: "accent" }, |
| 327 | { id: "events", title: "events" }, |
| 328 | { id: "b", title: "Agent B", sub: "`--follow`", tone: "external" }, |
| 329 | ], |
| 330 | steps: [ |
| 331 | { from: "a", to: "api", label: "`session` `start`, `harness_session_id`" }, |
| 332 | { from: "api", to: "work", label: "start, idempotent on that id" }, |
| 333 | { from: "a", to: "api", label: "`work_item` `work` #12" }, |
| 334 | { from: "api", to: "work", label: "item, repos, claims, sessions, pulls, blockers" }, |
| 335 | { from: "a", to: "api", label: "`work_item` `claim` #12" }, |
| 336 | { from: "api", to: "work", label: "claim: 30 min unless renewed" }, |
| 337 | { from: "work", to: "events", label: "`work.claimed`", async: true }, |
| 338 | { from: "api", to: "b", label: "its `list_events` long poll returns `work.claimed`", reply: true }, |
| 339 | { note: "B may still claim #12; it is told about A, and nothing is refused", over: ["work", "b"] }, |
| 340 | { phase: "While A works" }, |
| 341 | { from: "a", to: "api", label: "heartbeat, entries: renew the claim" }, |
| 342 | { note: "quiet for 15 min: idle; for 2 h: ended; noticed on the next read", over: ["api", "work"] }, |
| 343 | ], |
| 344 | }; |
| 345 | |
| 346 | // ── Compute ──────────────────────────────────────────────────────────────── |
| 347 | |
| 348 | const actionsJob: Sequence = { |
| 349 | kind: "sequence", |
| 350 | caption: "A workflow job on g1t’s runners, from the push that starts it to the status on the commit", |
| 351 | width: 900, |
| 352 | actors: [ |
| 353 | { id: "events", title: "events" }, |
| 354 | { id: "actions", title: "actions", sub: "services/actions", tone: "accent" }, |
| 355 | { id: "runner", title: "runner", sub: "services/runner" }, |
| 356 | { id: "sandbox", title: "Sandbox", sub: "`g1t-runner`", tone: "success" }, |
| 357 | { id: "api", title: "API", sub: "apps/api" }, |
| 358 | { id: "work", title: "work", sub: "services/work" }, |
| 359 | ], |
| 360 | steps: [ |
| 361 | { from: "events", to: "actions", label: "`git.push` on `g1t-events-actions`", async: true }, |
| 362 | { from: "actions", to: "actions", label: "match workflows, plan the jobs" }, |
| 363 | { from: "actions", to: "work", label: "`set_commit_status` pending" }, |
| 364 | { from: "actions", to: "runner", label: "`start_actions_job`" }, |
| 365 | { from: "runner", to: "sandbox", label: "a Container for this job alone" }, |
| 366 | { phase: "The job runs" }, |
| 367 | { from: "sandbox", to: "api", label: "`POST /actions/jobs/{job}`: steps, logs" }, |
| 368 | { from: "api", to: "actions", label: "`job_report`" }, |
| 369 | { from: "api", to: "sandbox", label: "go on, or cancel", reply: true }, |
| 370 | { note: "HTTP(S) leaves only through the runner’s egress handler", over: ["runner", "sandbox"] }, |
| 371 | { phase: "The job ends" }, |
| 372 | { from: "actions", to: "work", label: "`set_commit_status` success or failure" }, |
| 373 | { from: "actions", to: "events", label: "`workflow.completed`", async: true }, |
| 374 | ], |
| 375 | }; |
| 376 | |
| 377 | const sandboxes: Graph = { |
| 378 | kind: "graph", |
| 379 | caption: "Where a job runs: in a Container on g1t’s runners, or inside three walls on a machine of yours", |
| 380 | width: 940, |
| 381 | height: 330, |
| 382 | groups: [ |
| 383 | { x: 4, y: 4, w: 452, h: 322, label: "g1t’s runners: Cloudflare Containers", tone: "success" }, |
| 384 | { x: 24, y: 40, w: 230, h: 190, label: "one sandbox per job" }, |
| 385 | { x: 474, y: 4, w: 462, h: 322, label: "Your machine: three walls", tone: "success" }, |
| 386 | { x: 490, y: 40, w: 300, h: 274, label: "VM: WSL2, vfkit or QEMU" }, |
| 387 | { id: "sysbox", x: 506, y: 76, w: 268, h: 222, label: "Sysbox container: `g1t-runner`" }, |
| 388 | ], |
| 389 | nodes: [ |
| 390 | { id: "h-runner", x: 44, y: 78, w: 190, title: "`g1t-runner`", sub: "does one job, exits" }, |
| 391 | { id: "h-docker", x: 44, y: 156, w: 190, title: "its own dockerd", sub: "when `DOCKER=on`" }, |
| 392 | { id: "egress", x: 290, y: 78, w: 150, title: "egress handler", sub: "all HTTP(S) leaves here", tone: "accent" }, |
| 393 | { id: "hosts", x: 290, y: 250, w: 150, title: "allowed hosts", sub: "registries, your APIs", tone: "external" }, |
| 394 | { id: "job", x: 526, y: 116, w: 228, title: "job container", sub: "new for every job" }, |
| 395 | { id: "dockerd", x: 526, y: 210, w: 228, title: "its own dockerd", sub: "never the host’s `docker.sock`" }, |
| 396 | { id: "api", x: 802, y: 160, w: 130, title: "api.g1t.sh", sub: "long poll, outbound", tone: "external" }, |
| 397 | ], |
| 398 | edges: [ |
| 399 | { from: "h-runner", to: "egress" }, |
| 400 | { from: "egress", to: "hosts", label: "`g1tsec_…` → the secret" }, |
| 401 | { from: "sysbox", to: "api", fromSide: "right", toSide: "left" }, |
| 402 | ], |
| 403 | }; |
| 404 | |
| 405 | const deploymentPath: Graph = { |
| 406 | kind: "graph", |
| 407 | caption: "Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname", |
| 408 | width: 940, |
| 409 | height: 330, |
| 410 | nodes: [ |
| 411 | { id: "repos", x: 10, y: 30, w: 160, title: "repos", sub: "a push to a branch" }, |
| 412 | { id: "deployments", x: 260, y: 30, w: 170, title: "deployments", sub: "services/deployments", tone: "accent" }, |
| 413 | { id: "build", x: 520, y: 30, w: 180, title: "build sandbox", sub: "`g1t-runner`, `MODE=deploy`", tone: "success" }, |
| 414 | { id: "kv", x: 260, y: 150, w: 170, title: "KV `g1t-domains`", sub: "hostname → app", shape: "store" }, |
| 415 | { id: "wfp", x: 520, y: 150, w: 180, title: "dispatch namespace", sub: "`g1t-deployments`", tone: "accent" }, |
| 416 | { id: "telemetry", x: 760, y: 150, w: 170, title: "telemetry", sub: "tail events, per project" }, |
| 417 | { id: "visitors", x: 10, y: 268, w: 160, title: "Visitors", sub: "`*.g1t.page`, your domain", tone: "external" }, |
| 418 | { id: "pages", x: 260, y: 268, w: 170, title: "pages", sub: "services/pages", tone: "accent" }, |
| 419 | { id: "otel", x: 760, y: 268, w: 170, title: "Your software", sub: "OTLP to otel.g1t.sh", tone: "external" }, |
| 420 | ], |
| 421 | edges: [ |
| 422 | { from: "repos", to: "deployments", label: "`git.push`", async: true }, |
| 423 | { from: "deployments", to: "build", label: "`start_deploy`" }, |
| 424 | { from: "deployments", to: "wfp", fromSide: "bottom", toSide: "top", fromAt: 0.4, toAt: -0.4, bend: 116, label: "the built Worker" }, |
| 425 | { from: "deployments", to: "kv", fromAt: -0.2, toAt: -0.2, label: "domains" }, |
| 426 | { from: "visitors", to: "pages" }, |
| 427 | { from: "pages", to: "kv", label: "which app?" }, |
| 428 | { from: "pages", to: "wfp", fromSide: "right", toSide: "bottom", label: "`APPS.get(app)`" }, |
| 429 | { from: "wfp", to: "telemetry", async: true }, |
| 430 | { from: "otel", to: "telemetry", label: "OTLP" }, |
| 431 | ], |
| 432 | }; |
| 433 | |
| 434 | // ── Data and access ──────────────────────────────────────────────────────── |
| 435 | |
| 436 | /** Each port: its adapter on g1t.sh, the port, and its adapter when self-hosted, each with a second line. */ |
| 437 | const PORT_ROWS: [string, string, string, string, string, string][] = [ |
| 438 | ["Cloudflare Artifacts", "namespaces, sharded", "`GitStore`", "services/repos", "gitstore", "bare repositories, `git http-backend`"], |
| 439 | ["R2", "a bucket per use", "`BlobStore`", "crates/blobstore", "S3", "RustFS, MinIO, Ceph, AWS"], |
| 440 | ["Email Sending", "from g1t.sh", "`EMAIL`", "a `send_email` binding", "the mail Worker", "Mailpit, then your SMTP"], |
| 441 | ["Containers", "services/runner", "`RUNNER`", "a service binding", "the off Worker", "answers that it is off"], |
| 442 | ["D1, KV, Queues, DOs", "Cloudflare’s", "Workers bindings", "unchanged code", "workerd", "SQLite files on a volume"], |
| 443 | ]; |
| 444 | |
| 445 | const portsAndAdapters: Graph = { |
| 446 | kind: "graph", |
| 447 | caption: "Ports and adapters: the same service code, with a Cloudflare adapter on g1t.sh and an open one when you run it yourself", |
| 448 | width: 940, |
| 449 | height: 400, |
| 450 | groups: [ |
| 451 | { x: 6, y: 4, w: 262, h: 392, label: "On g1t.sh" }, |
| 452 | { x: 662, y: 4, w: 272, h: 392, label: "Self-hosted, in Docker Compose", tone: "success" }, |
| 453 | ], |
| 454 | nodes: PORT_ROWS.flatMap(([hosted, hostedSub, port, portSub, self, selfSub], i) => { |
| 455 | const y = 40 + i * 72; |
| 456 | return [ |
| 457 | { id: `h${i}`, x: 22, y, w: 230, h: 50, title: hosted, sub: hostedSub }, |
| 458 | { id: `p${i}`, x: 352, y, w: 230, h: 50, title: port, sub: portSub, tone: "accent" as const }, |
| 459 | { id: `s${i}`, x: 678, y, w: 240, h: 50, title: self, sub: selfSub, tone: "success" as const }, |
| 460 | ]; |
| 461 | }), |
| 462 | edges: PORT_ROWS.flatMap((_, i) => [ |
| 463 | { from: `p${i}`, to: `h${i}` }, |
| 464 | { from: `p${i}`, to: `s${i}` }, |
| 465 | ]), |
| 466 | }; |
| 467 | |
| 468 | const identityFlow: Graph = { |
| 469 | kind: "graph", |
| 470 | caption: "Every credential becomes the same viewer, resolved once per request; each service decides for itself", |
| 471 | width: 940, |
| 472 | height: 340, |
| 473 | groups: [{ x: 4, y: 4, w: 226, h: 332, label: "Credentials" }], |
| 474 | nodes: [ |
| 475 | { id: "cookie", x: 18, y: 32, w: 198, h: 50, title: "Session cookie", sub: "`g1t_session`, the site", tone: "external" }, |
| 476 | { id: "pat", x: 18, y: 92, w: 198, h: 50, title: "Access token", sub: "`g1t_…`, scoped", tone: "external" }, |
| 477 | { id: "oauth", x: 18, y: 152, w: 198, h: 50, title: "OAuth token", sub: "MCP clients, apps", tone: "external" }, |
| 478 | { id: "job", x: 18, y: 212, w: 198, h: 50, title: "Job or run token", sub: "one workflow job", tone: "external" }, |
| 479 | { id: "gitpw", x: 18, y: 272, w: 198, h: 50, title: "git password", sub: "refused with 2FA on", tone: "external" }, |
| 480 | { id: "identity", x: 286, y: 146, w: 160, h: 62, title: "identity", sub: "`user_for_…`", tone: "accent" }, |
| 481 | { id: "viewer", x: 556, y: 146, w: 170, h: 62, title: "the viewer", sub: "kind, workspaces, grants" }, |
| 482 | { id: "services", x: 836, y: 146, w: 98, h: 62, title: "services", sub: "`access::can`", tone: "accent" }, |
| 483 | ], |
| 484 | edges: [ |
| 485 | ...["cookie", "pat", "oauth", "job", "gitpw"].map((from) => ({ from, to: "identity", fromSide: "right" as const, toSide: "left" as const, bend: 256 })), |
| 486 | { from: "identity", to: "viewer", label: "once per request" }, |
| 487 | { from: "viewer", to: "services", label: "in every RPC body" }, |
| 488 | ], |
| 489 | }; |
| 490 | |
| 491 | // ── Shipping ─────────────────────────────────────────────────────────────── |
| 492 | |
| 493 | const STAGES: [string, string, string][] = [ |
| 494 | ["push", "a push to `main`", ""], |
| 495 | ["check", "check, plan", "manifest, tests"], |
| 496 | ["migrate", "migrations", "every pending one"], |
| 497 | ["core", "core", "the services"], |
| 498 | ["edge", "edge", "api, pages, status"], |
| 499 | ["front", "front", "site, sudo, docs"], |
| 500 | ["smoke", "smoke", "sign-in works"], |
| 501 | ]; |
| 502 | |
| 503 | const deployStages: Graph = { |
| 504 | kind: "graph", |
| 505 | caption: "How g1t ships itself: each stage starts only when the one before it succeeded", |
| 506 | width: 940, |
| 507 | height: 140, |
| 508 | groups: [{ x: 140, y: 4, w: 796, h: 132, label: "`.g1t/workflows/deploy.yml`, on g1t Actions", tone: "accent" }], |
| 509 | nodes: STAGES.map(([id, title, sub], i) => ({ |
| 510 | id, |
| 511 | x: 10 + i * 133, |
| 512 | y: 56, |
| 513 | w: 118, |
| 514 | h: 50, |
| 515 | title, |
| 516 | sub: sub || undefined, |
| 517 | tone: id === "push" ? ("external" as const) : id === "smoke" ? ("success" as const) : undefined, |
| 518 | })), |
| 519 | edges: STAGES.slice(1).map(([id], i) => ({ from: STAGES[i][0], to: id })), |
| 520 | }; |
| 521 | |
| 522 | export const ARCHITECTURE: Record<string, Spec> = { |
| 523 | "platform-map": platformMap, |
| 524 | "page-request": pageRequest, |
| 525 | "api-request": apiRequest, |
| 526 | "push-path": pushPath, |
| 527 | "clone-path": clonePath, |
| 528 | "event-fanout": eventFanout, |
| 529 | "follow-events": followEvents, |
| 530 | "merge-queue": mergeQueue, |
| 531 | coordination, |
| 532 | "actions-job": actionsJob, |
| 533 | sandboxes, |
| 534 | "deployment-path": deploymentPath, |
| 535 | "ports-and-adapters": portsAndAdapters, |
| 536 | "identity-flow": identityFlow, |
| 537 | "deploy-stages": deployStages, |
| 538 | }; |