Our Tech estate Context and architecture

C4 diagrams

Start at the estate, then zoom into Profit Admin. C4 here means Context (people and other systems), Container (running pieces), then Component (the important parts inside one piece). Read the caption under each picture before the boxes.

0. Context — the estate

flowchart LR
  shopper["UK shopper"]
  james["James / Cursor"]
  store["Shopify store"]
  mail["Store mailbox + track-proxy"]
  ads["Meta ads"]
  desk["Profit Admin"]
  suppliers["CJ / DSers / Alibaba"]
  shopper --> ads --> store
  shopper --> store
  shopper --> mail
  store --> suppliers
  mail --> suppliers
  james --> desk
  james --> store
  james --> ads
  desk --> store
  desk --> ads
  desk --> suppliers
        

The estate is the shop plus the back office. Profit Admin is one box: it reads money and writes decisions. It does not serve the storefront or hold supplier credentials — that is track-proxy.

1. Context — who talks to Profit Admin

flowchart LR
  operator["Store operator\nreads P and L and decisions"]
  agent["Cursor agent\nOAuth machine API"]
  access["Cloudflare Access\nis this email allowed in?"]
  pa["Profit Admin\nhosted ops desk"]
  shopify["Shopify\norders and catalog"]
  meta["Meta Ads\nspend and purchases"]
  cj["CJ Dropshipping\nlanded COGS"]
  operator --> access --> pa
  agent --> pa
  pa --> shopify
  pa --> meta
  pa --> cj
        

Access only answers “may this email reach the app?”. Membership in an organisation still comes from Profit Admin itself. Agents never log in as a human — they use an API key from the desk API page.

2. Containers — the running pieces

flowchart TB
  operator["Operator in a browser"]
  subgraph cloudflare [Cloudflare]
    desk["React desk"]
    worker["Gateway Worker\nauth, orgs, decisions, proxy"]
    d1[("D1\nusers, orgs, keys, decisions")]
    hub["DeskHub\nlive clock pings"]
    engine["Engine\nPython FastAPI, P and L"]
    disk[("Org files\nsnapshots and caches")]
  end
  operator --> desk --> worker
  worker --> d1
  worker --> hub
  worker --> engine
  engine --> disk
        

The React desk is a Worker asset. Opening a page does not start the engine. API calls and Refresh do. The engine sleeps after 2 minutes of quiet. Production's 15-minute cron wakes it; staging runs only the mail automation, hourly. The first call after sleep can take about a minute. D1 survives that sleep. Engine disk does not, which is why decisions live in D1. P&L still lives in Python. Do not reimplement it in the Worker.

3. Components — inside the gateway

flowchart LR
  subgraph worker [Gateway Worker]
    gate["Human gate\nemail session"]
    tenancy["Tenancy\norg, role, billing"]
    keys["Machine keys\nOAuth and API keys"]
    decisions["Decision store\nlist and upsert"]
    proxy["Engine proxy\nP and L and refresh"]
  end
  d1[("D1\nenvironment plus org")]
  engine["Engine\nPDF render"]
  gate --> tenancy --> d1
  keys --> tenancy
  decisions --> d1
  decisions -->|"pack JSON"| engine
  proxy --> engine
        

When you click PDF, the Worker loads rows from D1 and asks the engine to draw the file. Staging and production share one D1 database today; each row carries an environment so they do not overwrite each other.

3b. Components — inside the engine

flowchart LR
  subgraph engine [Python engine]
    collect["Collectors\nShopify, Meta, CJ"]
    pnl["pnl.py\nthe only P and L"]
    snap["Snapshots\nlatest.json and clocks"]
    render["Render\nPDF and Markdown"]
  end
  disk[("Org files")]
  collect --> snap
  pnl --> snap
  render --> snap
  snap --> disk
        

Refresh and webhooks update the snapshot. The 15-minute cron also refreshes ads and a short tracking batch. The desk polls clocks and reloads when numbers change. Ads Insights default to the whole ad account; optional split overlays campaign / ad set / ad. Improve jobs and DSers spreadsheet watches stay on the laptop — they are not in this Container.

Mail is its own subsystem inside the gateway and the engine. Its components, sequence diagrams and draft lifecycle are on the Mail system page.