Architecture and ownership
Deploy a modular system
The browser uses one same-origin application Worker for rendering, sign-in and Connect requests. A separate Email Worker has only intake permissions. A queue consumer parses alerts and computes results. They share versioned domain packages, not HTTP calls between small internal services.
React · TanStack
HTTPS + session cookie ↓
OIDC · Connect · authorization
owned repositories ↓
Facts · corrections · calculation revisions
↑ idempotent writes
Parse · classify evidence · calculate
↑ message ID only
Tag gate · size gate · durable intake
R2 holds raw MIME without a public URL. Queue bodies contain opaque intake IDs and versions, never email text or account details. A D1 outbox repairs the non-atomic boundary between R2, D1 and Queues.
Package boundaries
| Module | Owns | Must not do |
|---|---|---|
web |
Routes, forms, sheets, accessibility, Query keys | Calculate reward rules in components |
contract |
Protobuf descriptors and generated clients | Import database tables |
api |
Session, tenant authorization, request validation | Trust a supplied account ID |
intake |
Size limits, tag validation, receipts, MIME retention | Guess card ownership from sender |
parsers |
Versioned bank-template adapters and evidence extraction | Fetch remote email content or execute HTML |
ledger |
Facts, credits, revisions and deduplication | Infer that a category proves MCC |
rewards |
Pure deterministic evaluation of verified rule data | Call AI or read the clock implicitly |
projection |
Build complete result revisions and publish pointers | Mix results from different revisions |
jobs |
Outbox, retry, cleanup, deletion and reconciliation | Run unbounded account-wide scans |
Runtime contracts
Conceptual interfaces below describe responsibilities, not implementation code.
parseAlert(raw, parserVersion) -> ParsedEvidence | NeedsReview(reason)
resolveCapture(evidence, accountContext) -> PurchaseFact | CreditFact | HeldCapture
calculate(facts, ruleVersions, settingsVersions, asOf) -> CalculationRevision
publish(revision, expectedLedgerVersion) -> Published | Superseded
readSnapshot(account, selection) -> Snapshot(revision, completeness, provenance)
Use integer minor units for money and integer or rational reward units. Parse external strings at boundaries. Unknown amounts, dates, MCCs and payment methods are explicit variants. They never default to zero or an eligible state.
Why D1 for the pilot
D1 fits the existing Workers plan and supports transactional batches. Each database processes queries on one thread, so expensive calculations stay outside SQL write transactions. The pilot starts with one database per environment and indexed tenant queries. A measured contention or storage limit triggers a new ADR before expansion. D1 limits.
Postgres with Hyperdrive remains the alternative for heavier write concurrency, richer transactional workflows or a residency requirement D1 cannot meet. Hyperdrive is a connection layer, not a supplied Postgres server. No external database subscription is assumed.
Use Workers for the application runtime; the documentation pages are a separate static Pages artifact. TanStack Start has an official Workers deployment path. The Connect adapter, MIME parser and auth library still need a real Workers compatibility spike. Start on Workers.