Audit findings
4 parallel adversarial research agents stress-tested every library pick against May 2026 reality. 5 picks overturned, 2 with major caveats added, several minor corrections. This page is the consolidated diff vs the original plan.
Overturns (5)
1. Web framework: Next.js 15 → TanStack Start
Why overturn: Next.js 16 already shipping in 2026 — 15 is mid-life. Async params/searchParams migration burned production users. Caching default flip caused stale/no-cache surprises. Vercel ISR surprise-bills common.
Alternative: TanStack Start v1 (GA early 2025, mature mid-2026). Per Platformatic benchmark, fastest SSR React framework. End-to-end type-safe routes/search-params without codegen. Deploys to Vercel/Netlify/Cloudflare/any Node host — no Vercel lock-in. Server Components opt-in where useful, not mandatory.
Fit for SaaS: LogRocket and Alex Cloudstar both call out TanStack Start as the right choice for dashboards, SaaS, admin panels — exactly Spacehub's shape. We don't have marketing SEO surface to lose.
Action: decision-pending. If user agrees, swap apps/web scaffold before Phase 1 starts.
Sources: LogRocket comparison, Alex Cloudstar 2026, TanStack docs.
2. Background jobs: pg-boss + BullMQ hybrid → graphile-worker (or Hatchet)
Why overturn: Hybrid was wrong shape. Two delivery semantics, two failure modes, two dashboards. BullMQ has recurring production footguns — Redis OOM, stalled job recovery, eviction storms. pg-boss is single-maintainer (timgit), bus factor 1.
Alternative:
- graphile-worker (boring, proven) — v0.16.6, Postgres-native, battle-tested at GitHub. Free, no Redis. Outbox + cron in one tool.
- Hatchet (newer, more features) — Postgres-native, processing >1B tasks/month, 10k+ self-host deployments/mo. Durable workflows + cron + outbox unified.
- Inngest / Trigger.dev — managed alternatives, more $$ but better DX.
Recommended: graphile-worker for v2 (least new tech to learn, Postgres-only). Hatchet if we want durable workflows from day 1.
Drops Redis from the platform requirement for queues — Redis stays only for OTP + rate-limit cache.
Sources: graphile-worker, Hatchet adoption, 2026 comparison.
3. PDF: @react-pdf/renderer → Playwright PDF (or Gotenberg)
Why overturn: Memory leaks in long-running batch generation are unresolved (open issues #2217, #2848, #378, more). Cyrillic font rendering is literal open bug #1366 — exactly our case (Mongolian invoices).
Alternatives:
- Playwright PDF — faster than Puppeteer (147ms→42ms cold, 48ms→3ms warm). HTML+CSS Paged Media + web fonts = trivially correct Cyrillic. Best for our Phase 3 batch invoice generator.
- Gotenberg — containerized Chromium+LibreOffice microservice. Stateless, scales horizontally. PDF/A support (Mongolian archival compliance). Worth it if invoice volume justifies infra separation.
- pdf-lib — programmatic, best Unicode/custom font handling if invoice is overlay-on-fixed-form.
Recommended: Playwright PDF behind a queue for batches. Explicit browser.close() and instance recycling every N PDFs. Templates remain JSX-shaped (React components rendering to HTML, then Playwright prints HTML to PDF).
Sources: Cyrillic bug #1366, HTML→PDF benchmark 2026.
4. Hosting region: Hetzner Helsinki/Falkenstein → Hetzner Singapore
Why overturn: Hetzner Singapore went live August 2024. Mongolia→Singapore is ~80-100 ms; Mongolia→Helsinki is ~200-260 ms. We assumed EU was the only Hetzner option.
Caveats: Hetzner Singapore uses colocation (not their own DC). For a small SaaS this doesn't matter — get the latency.
Don't use Fly.io: removed permanent free tier in 2024, May 2026 Singapore proxy incident, operational woes flagged mid-2025. Not safe for production right now.
Sources: Hetzner Singapore press, Fly status history.
5. Gemini SDK: @google/generative-ai → @google/genai
Why overturn: @google/generative-ai was deprecated November 30, 2025. SDK releases after June 24, 2026 will strip deprecated modules. We had the wrong SDK in the plan.
Action:
- Use
@google/genai(unified Google GenAI SDK). - Use Vercel AI SDK v5+ (shipped July 2025) — v5 has UIMessage vs ModelMessage split, native SSE, tools use
inputSchema/outputSchema. - Plan for Gemini retry/fallback (Gemini 3 Pro
INVALID_ARGUMENTreports in the wild). - Hedge: AI SDK is multi-provider; OpenRouter as fallback proxy is cheap insurance.
Sources: deprecation notice, @google/genai npm, AI SDK 5.
Confirm with caveats (7)
better-auth — keep, add discipline
- NextAuth/Auth.js merged into Better Auth (discussion #13252) — strongest legitimacy signal.
- CVE-2026-32763 affects only the Kysely adapter (CVSS 8.2). We use Drizzle adapter — unaffected.
- Pin Drizzle 0.x until better-auth's adapter ships Drizzle 1.0 RC support (open issues #7691, #6766).
- Flutter SDK options (
flutter_better_auth) are marked "still under development, not fully tested" — wrap the bearer plugin manually with Dio + secure storage. Treat Flutter SDK as reference only. - Use our own SMS sender (131344) — don't use better-auth's managed SMS (Pro-tier only, useless for MN gateways).
Drizzle ORM — keep, review every migration
- Always use
migrate, neverpushfor RLS —drizzle-kit pushskips RLS policies (#3504). drizzle-kit generatewill emitDROP COLUMN,RENAME COLUMN,SET NOT NULL— each catastrophic on live Postgres. Review every generated SQL manually.- No automatic expand-contract. Write the 4-step rename yourself.
Hono — keep (or Fastify 5 if pure Node-on-VPS)
- 20M weekly downloads in 2026. Production users: Cloudflare D1/Workers KV, Deno, Clerk, Unkey, OpenStatus.
- Pin
hono+@hono/zod-openapiversions together — known breakage at version boundaries (#496, #946). - If you're not deploying to Workers/edge, Fastify 5 is the safer pick — JSON Schema validation built-in, Microsoft/Walmart/Discord-grade proof, deeper plugin ecosystem. Hono's multi-runtime value matters only if you actually plan edge deploy.
dinero.js v2 — keep, configure BigInt JSON serializer
- v2 stable shipped March 2 2026 after multi-year alpha purgatory. Sarah Dayan resumed maintenance.
JSON.stringify(1n)throws — must configure central BigInt serializer at REST boundary. Send as string. Never bare BigInt over HTTP.- Custom BigInt approach is defensible if MNT-only and <10 callsites. The moment we add USD/EUR or rounding modes, switch to dinero.
R2 + AWS SDK v3 — keep, add B2 backup for legal retention
- 2 real R2-specific incidents in 13 months: Feb 6 2025 (59-min full outage), Mar 21 2025 (1h7m, 100% writes failed, 35% reads).
- Custom-domain CORS bugs are well-documented and active. Test in staging.
- For invoice PDFs that legally must be retrievable, mirror critical objects to a second region or Backblaze B2 ($0.006/GB, 60% cheaper).
- For ephemeral assets, R2-only is fine.
- Class A operations are the silent cost driver — every multipart upload init/complete is Class A. Batch small uploads carefully.
Coolify — keep, but consider Dokploy if <4GB RAM
- Coolify v4 stable since April 2026.
- Apply CVE-2026-31431 patch.
- Dokploy uses ~0.8% idle CPU / ~350MB RAM vs Coolify's ~6%+. Switch if VPS is small.
- Kamal 2 if 1-dev shop wanting no UI surface. Bad for non-Claude teammates.
swagger_parser — keep, fix version number
- Real, healthy, actively maintained. Current version is v1.43.1 (~34 days ago), not 0.13.x — original plan had wrong version.
- Pin:
dio ^5.9,retrofit ^4.9,freezed ^3.2,retrofit_generator ^10.2. - Expect to work around:
allOf+required edge cases (#440), enum casing (#458), nested multipart (#444), circular schema refs (#423). Have manual-override path for 1-2 endpoints if needed. - Hand-rolled
retrofit+freezed(no spec generator) is genuinely competitive Plan B (2.1k likes / 427k downloads). - Don't use:
openapi_generator(17 months stale),swagger_dart_code_generator(locks to Chopper, no Dio/Freezed), rawopenapi-generator-cliJava (JVM in build pipeline, verbose built_value).
Minor corrections
Observability: consider Axiom over Grafana Loki for logs
Axiom: 500GB ingest/mo free, 30-day retention. Beats Grafana Loki's 50GB free at log volume. Loki has cardinality footguns at scale. Keep Sentry + OTel; route logs to Axiom.
FCM: pin firebase-admin ≥ v13
sendMulticast(), sendAll(), sendTo*() all removed in v13. Only sendEach() and sendEachForMulticast() work. Cap 500 tokens/call. Beware HTTP/2 local-dev bug (#2865) — pass enableLegacyTransport() for local only if it bites.
Resend: monitor Mongolia domain bounce rates
No public Mongolia deliverability benchmarks. Resend runs on Amazon SES (Tokyo available). Local providers (Mobicom mail relays) sometimes blocklist foreign SES IPs. Monitor bounce rate on @mobicom.mn / @magicnet.mn / @khanbank.com; swap to Postmark if >2%.
SMS: keep 131344 + MoceanAPI as fallback
If 131344 already works with sender ID, switching costs more than the savings. MoceanAPI Mongolia (~MNT 485/SMS, ~€0.116) is the Mongolia-specific fallback. International (Twilio/Plivo/Vonage) only if deliverability becomes an issue.
PostHog: cloud-only, never self-host
Free tier still 1M events/mo. Self-host break-even is 100M events/mo (Postgres + ClickHouse + Kafka + Redis = $5-15k/mo ops at low scale). Even a 1000-DAU SaaS won't approach that.
exceljs vs xlsx — confirmed
SheetJS xlsx CVE-2023-30533 (prototype pollution) patched only on cdn.sheetjs.com, NOT npm registry (GHSA-4r6h-8v6p-xvw6). For untrusted bank statement input, this is the exact attack surface. ExcelJS confirmed.
Summary — what changes in the plan
| Layer | Old pick | New pick | Where |
|---|---|---|---|
| Web | Next.js 15 | TanStack Start | decisions |
| Jobs | pg-boss + BullMQ hybrid | graphile-worker (or Hatchet) | jobs |
| @react-pdf/renderer | Playwright PDF | reports | |
| Hosting region | Hetzner FSN/HEL | Hetzner Singapore | devops |
| Gemini SDK | @google/generative-ai | @google/genai | chatbot |
| Codegen version | swagger_parser ^0.13 | ^1.43 | openapi-mobile |
| Logs | Grafana Loki | Axiom (consider) | observability |
| Backups | R2 only | R2 + B2 mirror for legal retention | files |
Sources used by audit agents
Each per-page update links to specific sources. Full transcripts in tmp/audit-agent-*.md if needed. Highlights: