Architecture — Dual-Surface, Single-Codebase, Multi-Tenant
Source: grant/architecture.md · synced 2026-06-12
One codebase. Two presentation surfaces. Multi-tenant from day 1. Calibre is tenant 1.
Phasing note (2026-06-03): The dual-surface architecture below is the target end state, mandated by Annex 4 (“marketable to other Headhunting Agencies”). Currently the project is building the pipeline (Steps 1–9) — CV/JD parsers, normalisers, matching engine. The productisation surface (signup, Stripe, multi-tenant RLS, demo tenants) ships as Step 10 (Sep–Nov 2026). The docs site at
bolun-ben-ship/calibreis a temporary public surface while Steps 1–9 build. See SOURCE-OF-TRUTH.md §2 for the 12-step system and §6 for the timeline.
High-Level Diagram
┌─────────────────────────────────────────────┐
│ ONE CODEBASE — Next.js (App Router) │
│ Multi-tenant from day 1 (tenant_id on │
│ every record, enforced by Supabase RLS) │
└─────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────────┐
│ app.calibre.sg │ │ <productisation-domain TBD> │
│ INTERNAL SURFACE │ │ AUDIT-FACING / SAAS SURFACE │
│ │ │ │
│ - Calibre recruiters │ │ - Marketing landing │
│ - Daily workflow │ │ - Pricing tiers │
│ - Focused UX │ │ - Signup → onboarding │
│ - Calibre = tenant 1 │ │ - Stripe checkout (test) │
│ - Pre-loaded data │ │ - Login → tenant dashboard │
│ │ │ - 1–2 demo agency tenants │
└──────────────────────────┘ └──────────────────────────────┘
│ │
└────────────┬───────────────┘
▼
┌─────────────────────────────────────────────┐
│ SHARED BACKEND │
│ │
│ Supabase (Postgres + RLS + Auth + Storage)│
│ Inngest (orchestration) │
│ Anthropic Claude API (primary) │
│ OpenAI API (fallback) │
│ │
│ External APIs (Calibre opex): │
│ - Apify (scraping) │
│ - Apollo (company/contact enrichment) │
│ - Anymail Finder (email verification) │
│ - Email send: Instantly / Smartlead │
└─────────────────────────────────────────────┘Why One Codebase, Not Two
| Two-codebase approach | One-codebase approach (chosen) |
|---|---|
| 2× maintenance burden forever | One codebase to maintain |
| Internal + audit versions can diverge | Architecture stays consistent |
| Audit may catch the divergence and question $28K spend | Single defensible product |
| Doubles training/documentation effort | One set of docs |
| If we ever sell, full rebuild needed | Already multi-tenant — flip a switch |
The audit-facing surface is not “a separate product.” It is the same product, with a public entry point — which is exactly what a real SaaS would have.
Tenancy Model
Pattern: shared schema, row-level isolation via Supabase RLS (Row-Level Security).
Every table has a tenant_id column. Every query is automatically filtered by the authenticated user’s tenant. Calibre is tenant_id = <calibre-uuid>.
Tables that need tenant_id:
candidatesjobs(employer-side BD)job_descriptions(Calibre’s mandates)outreach_messagesoutreach_threadstagscandidate_tagsrubricsvertical_configsmatch_scoresusers(recruiters belonging to a tenant)
Tables that are global (no tenant_id):
tenantssubscriptions(Stripe)audit_logvertical_templates(read-only seed data — Commodities, Maritime, Offshore, Tech & Gaming)
Surface Routing
Same Next.js app, different domains/routes. The productisation domain is TBD (previously planned getcalibre.ai, abandoned 2026-06-04); <prod> below is the placeholder. The current docs surface lives separately at calibredocs.3nm.io.
<prod>/ → marketing landing
<prod>/pricing → pricing tiers
<prod>/signup → signup form
<prod>/onboarding → multi-step onboarding wizard
<prod>/login → Supabase Auth login
<prod>/app → tenant dashboard (after login)
<prod>/admin → 3NMP super-admin (tenant management)
app.calibre.sg/ → redirects to <prod>/app for Calibre tenant
(Calibre staff bookmark this for direct access)Domain-based middleware in Next.js auto-routes Calibre’s domain straight into the logged-in app experience for their team, while the public domain shows marketing first.
Module Architecture (mapped to Annex 3)
The boxes below describe the app-route surfaces. The supporting business logic lives under
calibre-app/src/lib/*(see SOURCE-OF-TRUTH.md §4 for current code locations).
┌─ Module 1: AI-Powered Resourcing (12-step Step 4) ─────────────────┐
│ │
│ /resourcing/candidates → Apify orchestrator + filters │
│ /resourcing/employers → Apollo + Apify (job listings) │
│ /resourcing/research → AI deep-research worker (Inngest) │
│ /resourcing/tagging → Auto-tag engine (LLM categorisation) │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─ Module 2: AI Candidate Filtering & Matching (Steps 1-3, 5-8) ─────┐
│ │
│ /matching/jd-parser → LLM JD ingestion (Steps 1-2) │
│ /matching/jd-normaliser → Controlled-vocab tagger (Step 3) │
│ /matching/cv-normaliser → Controlled-vocab tagger (Step 5.2) │
│ /matching/layer1 → Deterministic hard-fit (Step 6) │
│ /matching/adjacency → Ratified + live-inference resolver │
│ /matching/layer2 → Evidence-grounded LLM (Step 7) │
│ /matching/layer3 → Verdict synthesis (Step 8) │
│ /matching/rubrics → Conditional mapping engine │
│ /matching/shortlist → Top-N ranking + UI │
│ /matching/verticals → Per-vertical term encyclopedia │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─ Module 3: AI-Driven Candidate Outreach ───────────────────────────┐
│ │
│ /outreach/personalisation → Deep research → email gen │
│ /outreach/sequences → Email + LinkedIn cadence │
│ /outreach/send → Instantly/Smartlead webhook │
│ /outreach/responses → Inbound parser → DB enrichment │
│ /outreach/bd → Employer-side BD outreach (thin) │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─ Cross-cutting: Productisation Layer ──────────────────────────────┐
│ │
│ /marketing → Landing, pricing, about │
│ /signup + /onboarding → Tenant creation wizard │
│ /billing → Stripe checkout + customer portal │
│ /admin → 3NMP super-admin │
│ /settings → Per-tenant config │
│ │
└─────────────────────────────────────────────────────────────────────┘Stack Decisions
| Layer | Tool | Why |
|---|---|---|
| Framework | Next.js (App Router) | Single codebase for marketing + app. Vercel-native. RSC for fast pages. |
| Database | Supabase (Postgres) | RLS gives multi-tenancy for free. Auth + Storage included. |
| Auth | Supabase Auth | Email + magic link + OAuth. Pairs with RLS. |
| Payments | Stripe | Industry standard. Test mode → live without code change. |
| Orchestration | Inngest | Better DX than n8n for code-defined workflows. Versioned, observable. |
| AI primary | Anthropic Claude | Quality reasoning for matching + research. |
| AI fallback | OpenAI | Cost/latency fallback for high-volume tagging. |
| Scraping | Apify (actors) | Mature LinkedIn/web scrapers. Per-tenant API key supported. |
| Enrichment | Apollo.io | Company + contact data. |
| Email validation | Anymail Finder | Verified emails for deliverability. |
| Email send | Instantly or Smartlead | Existing Calibre / 3NMP infra. |
| Hosting | Vercel + Supabase + Railway | Cheap, audit-friendly, fast iteration. |
Multi-Tenancy Details (Supabase RLS)
Example RLS policy for candidates table:
-- Read: only candidates belonging to the user's tenant
create policy "tenant_isolation_read"
on candidates for select
using (tenant_id = (auth.jwt() ->> 'tenant_id')::uuid);
-- Write: same restriction
create policy "tenant_isolation_write"
on candidates for insert
with check (tenant_id = (auth.jwt() ->> 'tenant_id')::uuid);The tenant_id is set as a custom claim in the user’s JWT at login. Every query the app makes automatically respects this — no application-level filtering needed.
Audit demo: during the auditor’s site visit, we can log in as Calibre, show their data; log out, log in as Demo Agency 2, show different data; same product, real isolation. This is the strongest possible defence of “marketable to other agencies.”
Build-Order Dependencies
1. Supabase schema + RLS + auth ← foundation
2. Calibre tenant seeding + recruiter logins ← internal usable
3. Module 1 (Resourcing — candidates first) ← Calibre's biggest pain
4. Module 2 (Matching) ← depends on Module 1 data
5. Module 3 (Outreach) ← depends on Modules 1+2
6. Module 1.5 (BD employer-side, thin) ← can parallel with 3
7. Marketing surface + signup + Stripe ← depends on multi-tenant working
8. Demo tenants + onboarding wizard polish ← audit prep
9. Training materials + recorded sessions ← audit prep
10. Project report draft ← audit prepWhat This Architecture Buys Us at Audit
- “Where did the $28K go?” → A real multi-tenant SaaS with marketing site, signup, payments, isolation. Not a Make.com scenario.
- “Is it marketable to other agencies?” → Demo tenants prove it. Stripe checkout proves monetisation path exists.
- “Is it proprietary?” → Conditional mapping engine, rubric DSL, vertical encyclopedias, scoring algorithm — these are proprietary code, not configured tools. Apify/Apollo are data ingestion infrastructure (Calibre’s opex).
- “What did Calibre actually use?” → Internal surface with their real data, real recruiters, real productivity gains measured.