Skip to Content
GrantArchitecture — Dual-Surface, Single-Codebase, Multi-Tenant

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/calibre is 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 approachOne-codebase approach (chosen)
2× maintenance burden foreverOne codebase to maintain
Internal + audit versions can divergeArchitecture stays consistent
Audit may catch the divergence and question $28K spendSingle defensible product
Doubles training/documentation effortOne set of docs
If we ever sell, full rebuild neededAlready 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:

  • candidates
  • jobs (employer-side BD)
  • job_descriptions (Calibre’s mandates)
  • outreach_messages
  • outreach_threads
  • tags
  • candidate_tags
  • rubrics
  • vertical_configs
  • match_scores
  • users (recruiters belonging to a tenant)

Tables that are global (no tenant_id):

  • tenants
  • subscriptions (Stripe)
  • audit_log
  • vertical_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

LayerToolWhy
FrameworkNext.js (App Router)Single codebase for marketing + app. Vercel-native. RSC for fast pages.
DatabaseSupabase (Postgres)RLS gives multi-tenancy for free. Auth + Storage included.
AuthSupabase AuthEmail + magic link + OAuth. Pairs with RLS.
PaymentsStripeIndustry standard. Test mode → live without code change.
OrchestrationInngestBetter DX than n8n for code-defined workflows. Versioned, observable.
AI primaryAnthropic ClaudeQuality reasoning for matching + research.
AI fallbackOpenAICost/latency fallback for high-volume tagging.
ScrapingApify (actors)Mature LinkedIn/web scrapers. Per-tenant API key supported.
EnrichmentApollo.ioCompany + contact data.
Email validationAnymail FinderVerified emails for deliverability.
Email sendInstantly or SmartleadExisting Calibre / 3NMP infra.
HostingVercel + Supabase + RailwayCheap, 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 prep

What This Architecture Buys Us at Audit

  1. “Where did the $28K go?” → A real multi-tenant SaaS with marketing site, signup, payments, isolation. Not a Make.com scenario.
  2. “Is it marketable to other agencies?” → Demo tenants prove it. Stripe checkout proves monetisation path exists.
  3. “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).
  4. “What did Calibre actually use?” → Internal surface with their real data, real recruiters, real productivity gains measured.
Last updated on