Bullpen
League opens in Part 3
A series in six parts · this is Part 1

Architecting software in 2026, built in public

A paper-trading league: play money, real US stocks and crypto at live prices, and a live leaderboard. Built in public for the blog series Architecting Software in 2026, one part at a time.

Paper trading. Play money. Not investment advice.

Empty
Your spot is open
There's no live leaderboard yet. The trading app's one live symbol is blocked on a compliant market-data source — Coinbase's terms forbid public display of their data, and no replacement has been chosen (issue #6).
The series

Six parts, one system

  1. PART 1NEXT
    Why distribute at all
    Not yet published
  2. PART 2PLANNED
    Architecture as code
    Not yet published
  3. PART 3PLANNED
    Data ownership and the first split
    Not yet published
  4. PART 4PLANNED
    The order saga
    Not yet published
  5. PART 5PLANNED
    The market open and the bill
    Not yet published
  6. PART 6PLANNED
    What held, what broke
    Not yet published
Architecture

The system, part by part

Generated straight from architecture/calm/moments/part-01.architecture.json via FINOS CALM — not drawn by hand. A new part changes this diagram for a reason you can point to.

Bullpen's Part 1 architecture: reader and player actors, the landing app and trading app, the price snapshot service and the external market-data provider.
Planned, not yet built
  • PART 2Architecture as codediagram not generated yet
  • PART 3Data ownership and the first splitdiagram not generated yet
  • PART 4The order sagadiagram not generated yet
  • PART 5The market open and the billdiagram not generated yet
  • PART 6What held, what brokediagram not generated yet
Decisions

The record so far

Parsed straight from architecture/adr/ at build time — context and decision excerpts, not paraphrases.

ADR-0001Accepted2026-09-25

Stack

Context. Bullpen is a public prototype and blog demo — a paper-trading league where players compete with play money on real US stock and crypto prices, with a live leaderboard. It is not a commercial product: there is no billing, no SLA, and no expectation of production traffic beyond what a blog audience generates.

Decision. Use Vercel's Hobby plan as the deployment target, a Turborepo monorepo managed with pnpm workspaces, and TypeScript as the implementation language across apps and packages. Free-tier managed services (Neon, Upstash, Grafana Cloud) are planned integrations for later parts of the series and are not wired up in Part 1.

ADR-0002Accepted2026-09-25

Why Distribute at All

Context. The 2010s microservices wave was largely driven by one problem: a monolith couldn't scale its hot path independently of the rest of the system, so teams split services to scale them separately. On modern per-request serverless compute — Vercel Functions and equivalents — that problem is already solved by the platform: every function autoscales independently by default. Splitting a system into services purely to "scale the hot path" no longer buys what it used to, because the hot path already scales without a split.

Decision. Reject "split by default" and "monolith forever" alike. Start Bullpen as one deployable unit conceptually — in practice two simple Next.js apps (landing and trading app) that share packages, neither of which is a "distributed system" in the interesting sense the rest of this series will explore.

ADR-0003Accepted2026-09-25

Monorepo

Context. Bullpen already spans multiple deployables (a landing app and a trading app) and shared code (a data model, UI, config) that both depend on. Later parts of the series add more moving pieces — the order saga, the Rust ingestion service — that will need to evolve alongside shared contracts without every change becoming a multi-repo coordination exercise. The build/test tooling also needs to scale with the codebase: as more apps and packages are added, CI shouldn't have to rebuild and retest everything on every change.

Decision. One Turborepo monorepo, organized as `apps/` (the deployables) and `packages/` (shared libraries, starting with `packages/contracts` for the shared data model), managed with pnpm workspaces. This gets affected-only builds and tests via Turborepo's task graph, local and remote caching, and enforced dependency boundaries between apps and libraries via `turbo boundaries`.

ADR-0005Accepted2026-09-26

Crypto Market Data from CoinGecko Demo

Context. Issue #6 (one live symbol) originally proposed the Coinbase Exchange public REST API for BTC-USD. Before writing any code against it, we read Coinbase's [Market Data Terms of Use](https://www.coinbase.com/legal/market_data), which forbid redistributing or displaying Market Data — including from the free/public Exchange endpoints — to any third party outside the operator's own organization, and forbid building an application for end users other than the operator. A public Bullpen page serving BTC-USD prices to visitors is exactly the use those clauses block. Full detail in `docs/data-sources.md`. That ruled Coinbase out under this project's own guardrail ("if the market-data terms forbid public display, stop"), leaving issue #6 blocked pending a replacement provider whose terms explicitly permit public display with attribution.

Decision. Use CoinGecko's free Demo API (`api.coingecko.com/api/v3`) for BTC-USD. `docs/data-sources.md` records the terms version read (5 Sept 2025), the attribution rule, the no-redistribution clause, the cache-refresh rule, and the Demo plan's limits.

Rules

What the codebase must hold to

Parsed from architecture/adl/structure.adl — Apps and libraries that exist in Part 1.

Structure

KindNamePath
SYSTEMBullpenbullpen
COMPONENTLandingapps/landing
COMPONENTTrading Appapps/web
LIBRARYUIpackages/ui
LIBRARYContractspackages/contracts

Asserted rules

  • every directory under apps/ and packages/ is DEFINED here
  • every DEFINED component and library exists as a directory
  • apps NEVER DEPEND ON other apps

Last check:arch run

PASSED2026-09-26T05:13:09.087Z
$ pnpm --filter @bullpen/fitness-checks run check:arch
check:arch passed: 5 ADL entries verified against /vercel/path0
$ tsx src/cli.ts
Cost

What this costs to run

Every allowance below is read from cost/allowances.json at build time and checked against its schema — the source link is the exact page it was verified against.

ServiceMetricAllowanceSource
vercel-hobbyactive_cpu_time4 hours/monthsource · checked 2026-09-25
vercel-hobbyprovisioned_memory360 GB-hrs/monthsource · checked 2026-09-25
vercel-hobbyfunction_memory_max2 GB per function (1 vCPU)source · checked 2026-09-25
vercel-hobbyfunction_max_duration300 seconds (default and maximum)source · checked 2026-09-25
vercel-hobbyfunction_invocations1,000,000 invocations/monthsource · checked 2026-09-25
vercel-hobbyedge_requests1,000,000 requests/monthsource · checked 2026-09-25
vercel-hobbydeployments_per_day100 deployments/daysource · checked 2026-09-25
vercel-queuesapi_operations1,000,000 operations/monthsource · checked 2026-09-25
vercel-workflowsworkflow_events50,000 events/monthsource · checked 2026-09-25
vercel-workflowsworkflow_data_written1 GB/monthsource · checked 2026-09-25
neonstorage0.5 GB/projectsource · checked 2026-09-25
neoncompute100 CU-hours/project/monthsource · checked 2026-09-25
upstash-redisdata_size256 MBsource · checked 2026-09-25
upstash-redismonthly_commands500,000 commands/monthsource · checked 2026-09-25
upstash-redismonthly_bandwidth10 GB/monthsource · checked 2026-09-25
grafana-cloudmetrics_active_series10,000 active seriessource · checked 2026-09-25
grafana-cloudlogs_ingest50 GB/monthsource · checked 2026-09-25
grafana-cloudtraces_ingest50 GB/monthsource · checked 2026-09-25
aws-lambdarequests1,000,000 requests/monthsource · checked 2026-09-25
aws-lambdacompute400,000 GB-seconds/monthsource · checked 2026-09-25
coingecko-demomonthly_calls10,000 calls/monthsource · checked 2026-09-26
coingecko-demorate_limit100 calls/minsource · checked 2026-09-26

Week 2026-w40 (2026-09-28 – 2026-10-04)

PENDING
ServiceMetricValue
vercel-hobbyactive_cpu_timenot yet measured
vercel-hobbyprovisioned_memorynot yet measured
vercel-hobbyfunction_invocationsnot yet measured
vercel-hobbyedge_requestsnot yet measured
vercel-hobbydeployments_per_daynot yet measured
vercel-queuesapi_operationsnot yet measured
vercel-workflowsworkflow_eventsnot yet measured
vercel-workflowsworkflow_data_writtennot yet measured
coingecko-demomonthly_callsnot yet measured

Week 40 has not happened yet as of when this ledger was created (2026-09-25). Actual usage gets filled in from the Vercel dashboard at or after the end of the week (2026-10-04), by a human or a later automated step. Until then every `value` below is null and `status` stays "pending" — do not replace these with invented or estimated numbers.

Cost per player: Estimate arrives in Part 5.