Skip to content

Architecture

The monorepo's layout, how each kind of unit is built, how a push to main becomes a staged deploy on g1t's own Actions, the tests that guard it, and how this section stays true.

g1t is built on g1t. Its source is at g1t.sh/flagon-io/g1t, every change is a pull request with required checks, and its own workflows deploy it.

Folder Holds
apps/ What people open: web (the site), api, status, sudo, docs, and the desktop (Tauri) and mobile (Expo) apps
services/ The services, one folder each, with their wrangler.jsonc and migrations
crates/ Shared Rust: contracts, kit, rules, scan, secrets, blobstore, actions, the runner binary and the g1t CLI
packages/ Shared TypeScript: contracts and theme (the design tokens)
deploy/ stack.jsonc, the inventory; self-host, the Docker Compose installation; runner and sandbox, the self-hosted runner’s images
scripts/ deploy.mjs and its helpers, releases, operations
plugins/ The Claude Code plugin, and setup for other coding agents
.g1t/workflows/ CI, deploys and releases, run by g1t Actions
conformance/ Checks that a job’s environment matches what workflows expect

deploy/stack.jsonc gives each unit a kind, and each kind builds its own way:

Kind Built by Units
rust-worker scripts/build-rust-worker.mjs: worker-build to WebAssembly, then wasm-opt The Rust services and apps/api
ts-worker Wrangler’s bundler The TypeScript services
react-router vite build, then Wrangler apps/web, apps/sudo
astro astro build apps/docs

The runner’s image is built in two layers: a large base with every toolchain, rebuilt only when its folder changes and recorded in services/runner/base.json, and a thin layer on top with the static g1t-runner binary.

How g1t ships itself: each stage starts only when the one before it succeededDiagram: How g1t ships itself: each stage starts only when the one before it succeeded - .g1t/workflows/deploy.yml, on g1t Actions: check, plan (manifest, tests); migrations (every pending one); core (the services); edge (api, pages, status); front (site, sudo, docs); smoke (sign-in works) - a push to main Arrows: - a push to main → check, plan - check, plan → migrations - migrations → core - core → edge - edge → front - front → smoke.g1t/workflows/deploy.yml, on g1t Actionsa push to maincheck, planmanifest, testsmigrationsevery pending onecorethe servicesedgeapi, pages, statusfrontsite, sudo, docssmokesign-in works
How g1t ships itself: each stage starts only when the one before it succeeded
How g1t ships itself: each stage starts only when the one before it succeeded
How g1t ships itself: each stage starts only when the one before it succeededDiagram: How g1t ships itself: each stage starts only when the one before it succeeded - .g1t/workflows/deploy.yml, on g1t Actions: check, plan (manifest, tests); migrations (every pending one); core (the services); edge (api, pages, status); front (site, sudo, docs); smoke (sign-in works) - a push to main Arrows: - a push to main → check, plan - check, plan → migrations - migrations → core - core → edge - edge → front - front → smoke.g1t/workflows/deploy.yml, on g1t Actionsa push to maincheck, planmanifest, testsmigrationsevery pending onecorethe servicesedgeapi, pages, statusfrontsite, sudo, docssmokesign-in works

A push to main starts .g1t/workflows/deploy.yml, one run at a time, never cancelled:

  1. Check and plan. The manifest is checked against every config and npm run test:deploy runs. Beside it, scripts/deploy.mjs plan works out which units changed since the commit each one is running.
  2. Migrations. Every pending D1 migration, before any code.
  3. Core, edge, front. The units in each stage deploy in parallel; a stage starts only when the one before succeeded. A unit binds only to units in its own stage or an earlier one, which a test checks, so new code never calls a service that has not shipped yet.
  4. Smoke. scripts/ops/smoke.mjs checks that sign-in and sign-up still work, without making an account.

Each run is one production deployment, shown on the commit as the deploy / production status. Only units whose inputs changed are deployed: their folder, or a crate or package they are built from, which deploy.mjs reads from Cargo’s and npm’s workspace metadata. Deploy g1t to Cloudflare covers the tool, rollbacks and a first deploy.

Some of what this section describes is enforced by tests, so a change that breaks it fails CI:

Test Fails when
npm run test:deploy A unit is missing from stack.jsonc, a binding crosses stages the wrong way, a unit is missing from How a self-hosted g1t runs, or Services and contracts disagrees with a wrangler.jsonc
cargo test -p g1t-contracts The Rust and TypeScript access rules or scopes differ
cargo test -p g1t-api openapi The docs’ copy of the OpenAPI document is out of date
app/lib/front-door-limits.test.ts in apps/web A wrangler.jsonc and packages/contracts/src/rate-limits.ts disagree
npm test in apps/docs A diagram’s arrow names a box that does not exist, two boxes overlap, or a label does not fit its box

CI (.g1t/workflows/ci.yml) runs the Rust tests, the TypeScript checks and tests, and builds the site, sudo and these docs on every pull request.

These pages describe the code on main. When you change how g1t is put together, change them in the same pull request:

If you change Update
A service’s bindings, storage or job Its row in Services and contracts and Data and storage
A request’s path, a limit or a deadline How a request is served
An event type or a route to a subscriber Events
The push, fetch or merge path Git and Work and the merge queue
Sandboxes, runners or egress Actions, runners and sandboxes
A new dependency on a Cloudflare product Its port in Data and storage

Diagrams are data, in apps/docs/src/diagrams/architecture.ts. Change the spec, and both the picture and the page’s Markdown copy change with it.