BullpenRead the series
See the six-part plan
A six-part architecture build log

Architecting software in 2026, built in public

A paper-trading league with play money, live crypto prices, and end-of-day U.S. stock prices. It is also the system I’m designing in the open for the series Architecting Software in 2026, one part a week.

Paper trading. Play money. Not investment advice.

Empty
Your spot is open
Orders are open in the trading app: sign in with GitHub and place a market buy. BTC-USD and end-of-day U.S. stock prices are live there.
Live prices
Powered by CoinGecko
The series

Six parts and a bonus, one system

Each part changes the architecture for a reason you can see: a new service, a new data owner, an event where there used to be a call.

  1. Part 1Published
    Why distribute at all
    Introduced
    One deployable, live BTC-USD
    Oct 4, 2026See it →
  2. Part 2Published
    Architecture as code
    Introduced
    Describe, govern and remember the design
    Oct 11, 2026See it →
  3. Part 3Published
    Who owns the data
    Introduced
    One owner per transaction, one Market Data service
    Oct 19, 2026See it →
  4. Part 4Published
    The order saga
    Introduced
    Fills, cancels and retries
    Oct 26, 2026See it →
  5. Bonus · DetourPublished
    Changing the plan mid-flight
    Introduced
    Player handles, moderated through a planned change
    Nov 11, 2026See it →
  6. Part 5Next
    The market open and the bill
    Introduced
    A load test, and cost as a check
  7. Part 6Planned
    What held, what broke
    Introduced
    The diff and the full bill
Architecture

The system, part by part

Scrub through the series to watch the architecture change. Select any part of the system to see what it does and which decision put it there.

Open the full architecture page

Overview · The bonusBuilt
What runs at the end of the bonus build: Part 4's system plus player handles, moderated. A signed-in player saves a handle; the rules are checked first, then one Boolean question goes to a decision model through Vercel AI Gateway. A probability strictly above the threshold approves it, and anything else, including every failure, goes to a review queue that is a Postgres table. A new owner, the handles schema, publishes one view, handles.approved_v1, which the leaderboard view joins; nothing pending, in review or rejected reaches a page.
SynchronousAsynchronousExternalNew in this part
Watch it travel through the current part's nodes, step by step.

The bonus nodes and connections

  • Reader (actor): A blog reader exploring Bullpen's architecture timeline on the landing site.
  • Player (actor): A visitor who sees live crypto and end-of-day stock prices and the leaderboard. Signing in with GitHub gives a player an account; the order ticket, the orders list showing the seven states, and the lifecycle behind them are built and tested.
  • Daily ingest cron (infra): Vercel's scheduler: once a day after the U.S. close it calls the Market Data service's ingest, with a secret. Decisions: ADR-0004 Deploy topology: Vercel Services in one project, ADR-0013 One Market Data owner.
  • Bullpen Landing (app): The public landing site with the architecture timeline explorer. Decisions: ADR-0003 Monorepo, ADR-0006 React Flow for the architecture explorer, ADR-0009 Guardrails are enforced, not requested.
  • Bullpen Trading App (app): The trading app. It owns orders and their lifecycle, reservations, fills, the ledger, positions and the leaderboard view in the trading schema, shows end-of-day stock prices read through prices_v1, signs players in with GitHub, lets a signed-in player set a handle and moderates it, and serves the authenticated sweep route the sweeper calls. Decisions: ADR-0001 Stack, ADR-0003 Monorepo, ADR-0012 One database, a schema and a role per transactional owner, ADR-0014 The leaderboard as a materialized view, ADR-0017 The order lifecycle is a Postgres state machine; the saga is for time, not for a new service, ADR-0018 The sweeper is a daemon that calls a route in the Trading App.
  • Market Data Service (service): Replaces the Price Snapshot Service: the CoinGecko price route moved into it. It owns both providers and both call budgets, and is the only writer of the market_data schema. A boundary in one release, not an independently deployed microservice. Decisions: ADR-0004 Deploy topology: Vercel Services in one project, ADR-0013 One Market Data owner.
  • CoinGecko (ext): Crypto prices. Called only by the Market Data service, inside a monthly call budget, with the key in a header. Decisions: ADR-0005 Crypto prices from CoinGecko.
  • Alpha Vantage (ext): U.S. stock prices, end of day, one request per symbol, fetched once a day by the Market Data service inside a daily call budget. Decisions: ADR-0008 U.S. stock prices from Alpha Vantage, end of day.
  • Google Analytics 4 (ext): Page views and a short list of events, only after the visitor accepts. Decisions: ADR-0010 Product analytics with Google Analytics 4 and Microsoft Clarity, behind consent.
  • Microsoft Clarity (ext): Heatmaps and session recordings, only after the visitor accepts. Decisions: ADR-0010 Product analytics with Google Analytics 4 and Microsoft Clarity, behind consent.
  • trading schema (data): Accounts, orders with a state and a version, reservations, fills, order events, the ledger, positions and the leaderboard view. Every step of an order's life is one transaction here. Decisions: ADR-0012 One database, a schema and a role per transactional owner, ADR-0017 The order lifecycle is a Postgres state machine; the saga is for time, not for a new service.
  • market_data schema (data): Quotes, the provider call ledger, and the view prices_v1. Decisions: ADR-0012 One database, a schema and a role per transactional owner, ADR-0013 One Market Data owner.
  • market_data.prices_v1 (data): The price contract: symbol, price, as-of time, source and a stale flag. The only thing outside market_data that trading may read. Decisions: ADR-0012 One database, a schema and a role per transactional owner.
  • Bullpen Database (data): One Neon project and one database, with a schema and a plain role per owner. Decisions: ADR-0012 One database, a schema and a role per transactional owner, ADR-0014 The leaderboard as a materialized view.
  • Sweeper (infra): A daemon whose only job is to call the trading app's sweep route and wait: every 5 seconds while an order is open, backing off to every 60 minutes when none is. It reads no database URL and holds no data. Built and tested locally; declared in vercel.json, it runs in production deployments only. Decisions: ADR-0018 The sweeper is a daemon that calls a route in the Trading App.
  • Neon Auth (ext): Neon's managed sign-in, enabled on the production branch with GitHub as its only provider. The trading app checks the access token it issues against its public keys and stores only the user's id. Decisions: ADR-0020 Sign-in is Neon's managed auth, GitHub only.
  • handles schema (data): Schema handles, owned by the role handles_owner: one row per player's handle with its status and the model's probability, the review queue as a table, and the view approved_v1. Decisions: ADR-0027 Player handles are moderated by a decision model and a review queue.
  • handles.approved_v1 (data): The handle contract: owner user id and handle, for approved handles only. The only thing outside handles that trading may read, and the reason a pending, review or rejected handle cannot reach a page. Decisions: ADR-0027 Player handles are moderated by a decision model and a review queue.
  • Vercel AI Gateway (ext): Vercel's gateway to models, called once per new or changed handle with model typesafe-ai/jev: one Boolean question, is this handle acceptable to show publicly? Only the handle text is sent. A failure of any kind sends the handle to review; there is no fallback model. Decisions: ADR-0027 Player handles are moderated by a decision model and a review queue.
  • Reader → Bullpen Landing (sync)
  • Player → Bullpen Trading App (sync)
  • Daily ingest cron → Market Data Service (async)
  • Bullpen Landing → Market Data Service (sync)
  • Bullpen Trading App → Market Data Service (sync)
  • Market Data Service → CoinGecko (sync)
  • Market Data Service → Alpha Vantage (sync)
  • Bullpen Trading App → trading schema (sync)
  • Bullpen Trading App → market_data.prices_v1 (sync)
  • Market Data Service → market_data schema (sync)
  • Bullpen Landing → Google Analytics 4 (async)
  • Bullpen Landing → Microsoft Clarity (async)
  • Bullpen Trading App → Google Analytics 4 (async)
  • Bullpen Trading App → Microsoft Clarity (async)
  • Sweeper → Bullpen Trading App (sync)
  • Bullpen Trading App → Neon Auth (sync)
  • Bullpen Trading App → handles schema (sync)
  • Bullpen Trading App → Vercel AI Gateway (sync)
  • Bullpen Trading App → handles.approved_v1 (sync)
Decisions

Written down, then built

I write every decision down before I build it. Open one to read the context, the decision and what it costs.

6 of 27 decisions
Rules

Checked, not hoped for

CI runs these checks on every pull request. Each rule is the line from structure.adl as I wrote it, with the result from the last snapshot I committed.

View structure.adl
61/61
Rules passing
last snapshot
2026-11-10 · cc968fb

What’s defined

10 entries
SYSTEM
1
COMPONENT
4
LIBRARY
5
Cost

What it costs to run

pending
Spent this month
Arrives in Part 5
Per-player cost estimate

Neon is on its Launch plan, which bills by use, and its usage for 2026-w42 has not been read. I read the amount from the console; nothing here is estimated.

24 free-tier allowances tracked, checked 2026-10-16.

All 24 allowances and sources
ServiceMetricAllowanceSource
Vercel HobbyActive CPU4 hours a monthsource · checked 2026-09-25
Vercel HobbyProvisioned memory360 GB-hrs a monthsource · checked 2026-09-25
Vercel HobbyFunction memory (max)2 GB per function (1 vCPU)source · checked 2026-09-25
Vercel HobbyFunction duration (max)300 seconds (default and maximum)source · checked 2026-09-25
Vercel HobbyFunction invocations1,000,000 invocations a monthsource · checked 2026-09-25
Vercel HobbyEdge requests1,000,000 requests a monthsource · checked 2026-09-25
Vercel HobbyDeployments100 deployments a daysource · checked 2026-09-25
Vercel WorkflowsWorkflow events50,000 events a monthsource · checked 2026-09-25
Vercel WorkflowsWorkflow data written1 GB a monthsource · checked 2026-09-25
NeonStorage1 GB/projectsource · checked 2026-10-13
NeonCompute100 CU-hours/project a monthsource · checked 2026-10-13
NeonProjects100 projectssource · checked 2026-10-13
NeonNetwork transfer5 GB/project a monthsource · checked 2026-10-16
Neon LaunchNetwork transfer500 GB/project a monthsource · checked 2026-10-16
Neon LaunchCompute rate0.106 USD per CU-hoursource · checked 2026-10-16
Neon LaunchStorage rate0.35 USD per GB-monthsource · checked 2026-10-16
CoinGecko DemoMonthly calls10,000 calls a monthsource · checked 2026-09-26
CoinGecko DemoRate limit100 calls a minutesource · checked 2026-09-26
Stock data providerDaily calls25 calls a daysource · checked 2026-10-13
Vercel ServicesService requests1,000,000 requests a monthsource · checked 2026-10-13
Vercel Queues · Part 5API operations1,000,000 operations a monthsource · checked 2026-09-25
Grafana Cloud · Part 5Active metric series10,000 active seriessource · checked 2026-09-25
Grafana Cloud · Part 5Log ingest50 GB a monthsource · checked 2026-09-25
Grafana Cloud · Part 5Trace ingest50 GB a monthsource · checked 2026-09-25

Free-tier usage

2026-w42 · 2026-10-12 – 2026-10-18 · pending
  • Vercel HobbyActive CPU
    usage pendingallowance checked 2026-09-25
  • Vercel HobbyProvisioned memory
    usage pendingallowance checked 2026-09-25
  • Vercel HobbyFunction invocations
    usage pendingallowance checked 2026-09-25
  • Vercel HobbyEdge requests
    usage pendingallowance checked 2026-09-25
  • Vercel HobbyDeployments
    usage pendingallowance checked 2026-09-25
  • NeonStorage
    usage pendingallowance checked 2026-10-13
  • NeonCompute
    usage pendingallowance checked 2026-10-13
  • NeonNetwork transfer
    usage pendingallowance checked 2026-10-16
  • CoinGecko DemoMonthly calls
    usage pendingallowance checked 2026-09-26
  • Stock data providerDaily calls
    usage pendingallowance checked 2026-10-13
  • Vercel ServicesService requests
    usage pendingallowance checked 2026-10-13