Runtime: TypeScript + Hono + Drizzle. Worker (default) + container. Hono and
Drizzle both run on both targets; entry points are thin.
Data: SQLite dialect everywhere. Hosted = D1, one database per organization
(tenancy is a connection). Self-host container = one SQLite file. Current
recovery is D1 Time Travel or a stopped-container physical snapshot; the D18
nightly R2 and portable logical exports remain tracked by #37 and #28.
D1 limits designed against: 100 bound params/statement, 1000 statements/invocation,
30s/query, 10 GB/db.
D1 reads retry once the connection drops: a read-only statement that fails with
D1_ERROR: Network connection lost is retried at most twice (50 ms, then 200 ms)
before the original error surfaces. This lives in one place, the idempotency
wrapper every Worker request's D1 handle goes through
(packages/db/src/d1-read-retry.ts, applied in idempotency.ts). Writes and
batches are never retried, since a write that lost its answer may already have
been applied; any other error is not retried either.
API: REST /api/v1 (canonical, honest) + /harvest/v2 shim (compat quirks at
the serializer only). OpenAPI generated and published; cli/mcp/mobile/extension
consume generated clients. Webhooks are additive (Harvest has none).
Auth: pluggable Identity seam. Access impl first (verify JWT signature
against team-domain JWKS; never bare CF-Access-* headers). Native magic-link +
password at v1.0.
Email: pluggable Mailer, HTTP-first (Workers cannot SMTP; SMTP is
container-only). AWS SES v2 is the first implementation, using SigV4 from
the Worker and lifted from ConflictHQ/mailsend. Every send is a queued job
with a logged delivery outcome. Suppression is checked before send; receipts
retain the SES message ID, request ID, and latency. Send as the user's domain
(SPF/DKIM/DMARC aligned), from a dedicated sending subdomain so transactional
mail cannot damage the deliverability of humans' inboxes on the apex.
Money: users-get-paid only. Checkout shape (Stripe/PayPal/QBO pay links +
webhooks) and reconciliation shape (Mercury: reference tokens, suggested-match
queue, unmatched state). We never hold funds. No billing code exists here.
Jobs: Cloudflare Queues on Workers / in-process queue in container, one
interface. Payment reminders are rows booked when an invoice is sent (from
the invoice's own policy, the organisation default, or the date named on the
send) and cancelled on payment; the daily cron on Workers, or the outbox tick
in the container, sends the ones that have come due and books the next.
Setting the organisation default also books it for the open invoices already
out, and an invoice can switch its own reminders off (the Reminders fact on
the invoice page), which cancels what is pending and outlives any default.