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.
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.
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.
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.
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.
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.