//ARCHITECTURE
How Verdict works
Eleven stages, run in this order for a reason: identity resolution before spend, extraction before verification, verification before scoring, scoring before a draft is ever written.
- //00
Validate + idempotency
Ordinary code. Required fields, email/domain normalization. Replaying a submissionId returns the existing result — never reprocesses.
- //01
Identity resolution
Runs BEFORE enrichment/scoring, deliberately. Exact email or domain match only counts as "confident" — name similarity, even a perfect score, never auto-merges.
- //02
Classify message
Haiku labels the message into approved categories. Vendor pitches, job applications, and student research hard-disqualify here, before any research spend.
- //03
Find research source
Looks up a domain in the seeded fixture corpus. An ambiguous or missing identity means no safe source to pick — enrichment correctly returns nothing rather than guessing.
- //04
Extract facts
Haiku reads the source page and extracts only explicitly stated facts into a fixed field schema, with a verbatim supporting quote for each. There is no field for a score, a discount, or an instruction — structurally, nothing else can land here.
- //05
Verify facts
Two independent checks per fact: a deterministic quote-in-source substring check (free, catches fabricated quotes), then a batched Haiku pass judging entailment and flagging quotes that read as instructions rather than facts. Only what survives both becomes "verified".
- //06
Evidence-sufficiency gate
Ordinary code. Counts resolved ICP criteria against a floor. Below the floor: no score is ever computed — the lead returns the specific questions that would unblock it.
- //07
Score (if the gate is open)
Deterministic points-based scoring across 7 criteria, each with cited evidence. A veto on B2B-fit or region disqualifies regardless of the numeric total.
- //08
Propose CRM change set
A diff, never a write. Possible identity matches propose nothing — a human resolves the merge. Confident matches propose an update; new leads propose a create.
- //09
Draft outreach (gated)
Only for qualified bands, and only using the lead's own message plus verified facts. Every claim the draft makes is independently re-verified against that same grounding text — never trusted from the drafting call's self-report.
- //10
Audit + jobs
Every stage above writes a started/completed/failed audit event with duration. A Postgres jobs table tracks attempts — the durable queue, not an external service.
Stack
- Next.js 16 (App Router) + TypeScript, Supabase Postgres (RLS-locked, service-role only)
- Claude Haiku for every model call, temperature pinned to 0
- Durable job queue: a Postgres table with attempts/backoff — no external queue service
- Enrichment reads a seeded fictional “mini-web”, not the live internet — deterministic, $0, every citation a real in-app URL
- Rate limiting + a race-safe daily spend cap (Postgres
FOR UPDATE, not read-then-write)