|
1 | 1 | # Unhold |
2 | 2 |
|
3 | | -**Guided case workspace for bank-account and UPI restrictions in India.** |
| 3 | +**Organise a bank-account or UPI freeze case in India. Draft a letter. You send it.** |
4 | 4 |
|
5 | | -Live: [unhold.live](https://www.unhold.live) · Demo (no account): [unhold.live/demo](https://www.unhold.live/demo) |
6 | | - |
7 | | -Unhold turns a freeze into a structured file: what happened, which papers you have, what to ask next, and a letter you can copy or print. **You send everything.** The product never emails a bank, police station, or regulator, and never claims an account will be released. |
| 5 | +| | | |
| 6 | +|---|---| |
| 7 | +| Live | [unhold.live](https://www.unhold.live) | |
| 8 | +| Demo (no account) | [unhold.live/demo](https://www.unhold.live/demo) | |
| 9 | +| Start a case | [unhold.live/start](https://www.unhold.live/start) | |
| 10 | +| Open a saved case | [unhold.live/open-case](https://www.unhold.live/open-case) | |
| 11 | +| Health | [unhold.live/healthz](https://www.unhold.live/healthz) | |
8 | 12 |
|
9 | 13 | [](https://github.com/thribhuvan003/unhold/actions/workflows/ci.yml) |
10 | 14 | [](LICENSE) |
11 | 15 |
|
12 | | ---- |
| 16 | +Unhold is a case file for a restricted account: facts, papers, next steps, and a letter you can copy or print. **Nothing is emailed to a bank, police station, or regulator on your behalf.** No unfreeze guarantee. |
13 | 17 |
|
14 | | -## For reviewers (≈3 minutes) |
| 18 | +--- |
15 | 19 |
|
16 | | -1. Open the [worked example](https://www.unhold.live/demo) at a phone width. |
17 | | -2. Read the server-only data boundary in [`supabase/migrations/20260713194008_server_only_data_boundary.sql`](supabase/migrations/20260713194008_server_only_data_boundary.sql) and the escalation gates in [`lib/escalations/proof-gates.ts`](lib/escalations/proof-gates.ts). |
18 | | -3. From a clean checkout: `pnpm install --frozen-lockfile && pnpm verify`. |
| 20 | +## Try it in three minutes |
19 | 21 |
|
20 | | -That path exercises lint, TypeScript strict mode, unit + contract tests, the no-auto-send guard, and a production build. |
| 22 | +1. Phone width → open the [demo](https://www.unhold.live/demo). |
| 23 | +2. Skim the safety boundary: [`docs/PRODUCT_AND_SAFETY.md`](docs/PRODUCT_AND_SAFETY.md). |
| 24 | +3. From a clean clone: `pnpm install --frozen-lockfile && pnpm verify`. |
21 | 25 |
|
22 | 26 | --- |
23 | 27 |
|
24 | | -## What it does |
| 28 | +## What you get |
25 | 29 |
|
26 | | -| Area | Behaviour | |
| 30 | +| Step | What happens | |
27 | 31 | | --- | --- | |
28 | | -| Intake | Short questions → structured case facts. Guest flow with a recovery code; no mandatory signup. | |
29 | | -| Evidence | Private storage, SHA-256 confirm, consent-gated extraction, human review when confidence is low. | |
30 | | -| Drafts | Case-aware letter text from recorded facts. Citations limited to a maintained allowlist. | |
31 | | -| Escalation | Later steps stay locked until required earlier proof exists (deterministic gates). | |
32 | | -| Follow-up | Idempotent jobs, monitored deadlines, full case history across devices. | |
33 | | -| Control | Copy / print / open-mail only. Nothing is sent on the user’s behalf. | |
| 32 | +| Start | Short questions. Guest session + recovery code. No forced signup. | |
| 33 | +| Papers | Private upload, hash check, readable/unreadable flags. You still re-read every file. | |
| 34 | +| Letter | Draft from your answers. You fill blanks, then copy / print / open mail yourself. | |
| 35 | +| Later steps | Locked until earlier send-proof is on file. | |
| 36 | +| Follow-up | Reminders only if you opt in. Full history if you reopen the case. | |
34 | 37 |
|
35 | | -Product language and non-negotiables: [`docs/PRODUCT_AND_SAFETY.md`](docs/PRODUCT_AND_SAFETY.md). |
| 38 | +If someone is losing money to active fraud right now: **1930**. |
36 | 39 |
|
37 | 40 | --- |
38 | 41 |
|
39 | | -## Safety boundary |
40 | | - |
41 | | -Unhold is a document-organisation tool, not a law firm, bank, or government service. Freeze rules differ by authority, bank, and facts. The UI labels uncertainty, attaches sources and review dates to legal positions, and avoids release guarantees or fixed timelines. |
42 | | - |
43 | | -If someone is in immediate danger of fraud loss, the public cyber helpline in India is **1930**. |
44 | | - |
45 | | ---- |
| 42 | +## How it is built |
46 | 43 |
|
47 | | -## Architecture |
48 | | - |
49 | | -```mermaid |
50 | | -flowchart LR |
51 | | - Browser["Browser: session + UI"] --> API["Next.js API routes"] |
52 | | - API --> DB["Supabase Postgres"] |
53 | | - API --> Store["Private object storage"] |
54 | | - API --> Queue["agent_jobs queue"] |
55 | | - Queue --> Agents["Intake · Verifier · Drafter · Bundle · Escalator"] |
56 | | - Monitor["Cron + on-demand kick"] --> Queue |
57 | | - Agents --> Gates["Zod + redaction + proof gates"] |
| 44 | +```text |
| 45 | +Browser → Next.js (app + /api/v1) → Supabase (Postgres + private storage) |
| 46 | + ↘ agent_jobs (classify / OCR / draft) |
58 | 47 | ``` |
59 | 48 |
|
60 | | -**Data plane** |
61 | | - |
62 | | -- Browser Supabase client is used for **auth only**. |
63 | | -- Application tables and RPCs are not granted to `anon` / `authenticated`. |
64 | | -- Authorised server routes use the service role after owner / collaborator / operator checks. |
65 | | -- Evidence and sealed bundles live in private buckets; uploads are size- and type-constrained. |
66 | | - |
67 | | -**Agent plane** |
68 | | - |
69 | | -- Jobs are idempotent, claimed with a DB lock, retried with bounded backoff, and dead-lettered after max attempts. |
70 | | -- Model output is untrusted until it passes schema validation, citation controls, redaction, and the relevant proof gate. |
71 | | -- Deterministic templates keep the core letter path usable when a provider is down. |
72 | | - |
73 | | -**Upload / OCR path (latency)** |
74 | | - |
75 | | -- Large phone photos are resized in the browser before PUT. |
76 | | -- Confirm returns after integrity checks; vision OCR runs after the response (`after()` + queue kick). |
77 | | -- Vision inputs are downscaled server-side; HEIC/WebP are normalised to JPEG when needed. |
78 | | - |
79 | | -### Model routing |
80 | | - |
81 | | -| Workload | Primary | Fallback | |
82 | | -| --- | --- | --- | |
83 | | -| Text (classify / draft) | Groq `openai/gpt-oss-120b` | NVIDIA `meta/llama-3.3-70b-instruct` | |
84 | | -| Vision (document OCR) | Groq `qwen/qwen3.6-27b` | NVIDIA `minimaxai/minimax-m3` | |
85 | | -| Embeddings (retrieval) | NVIDIA `nvidia/nv-embedqa-e5-v5` | Template path (no model) | |
86 | | - |
87 | | -### Stack |
| 49 | +- Browser talks to Supabase for **auth only**. Case data goes through server routes. |
| 50 | +- Jobs are claimed with a DB lock, retried with backoff, and reclaimed if a worker dies. |
| 51 | +- Templates cover the letter path when a model provider is down. |
88 | 52 |
|
89 | 53 | | Layer | Choice | |
90 | 54 | | --- | --- | |
91 | | -| App | Next.js 16 (App Router), React 19, TypeScript strict | |
| 55 | +| App | Next.js 16, React 19, TypeScript strict | |
92 | 56 | | Data | Supabase Postgres + private Storage | |
93 | | -| Jobs | DB queue; GitHub Actions every 5 minutes + request-time kick | |
94 | | -| Edge | Vercel, region `bom1` (Mumbai) | |
95 | | -| i18n | English + Hindi (`next-intl`) | |
96 | | -| Limits | Upstash Redis when configured; in-memory fallback locally | |
| 57 | +| Host | Vercel `bom1` (Mumbai) | |
| 58 | +| Jobs | DB queue + GitHub Actions every 5 minutes + request kick | |
| 59 | +| Languages | English + Hindi | |
97 | 60 |
|
98 | | -Hobby-plan cron limits and external drain wiring: [`docs/DEPLOY_VERCEL_HOBBY.md`](docs/DEPLOY_VERCEL_HOBBY.md). |
| 61 | +Deploy notes for Hobby cron limits: [`docs/DEPLOY_VERCEL_HOBBY.md`](docs/DEPLOY_VERCEL_HOBBY.md). |
99 | 62 |
|
100 | 63 | --- |
101 | 64 |
|
102 | | -## Local setup |
| 65 | +## Local |
103 | 66 |
|
104 | | -**Requires:** Node.js ≥ 22.14, pnpm 10.12.1. |
| 67 | +Node ≥ 22.14 · pnpm 10.12.1 |
105 | 68 |
|
106 | 69 | ```bash |
107 | 70 | git clone https://github.com/thribhuvan003/unhold.git |
108 | 71 | cd unhold |
109 | 72 | pnpm install --frozen-lockfile |
110 | | -cp .env.example .env.local |
111 | | -# fill local/test values only — never commit a filled env file |
| 73 | +cp .env.example .env.local # local values only — never commit secrets |
112 | 74 | pnpm dev |
113 | 75 | ``` |
114 | 76 |
|
115 | | -Environment catalogue: [`config/VERCEL_ENV_KEYS.md`](config/VERCEL_ENV_KEYS.md). |
116 | | -Apply SQL under `supabase/migrations/` in order against your project before exercising auth or storage. |
117 | | - |
118 | | ---- |
119 | | - |
120 | | -## Verification |
| 77 | +Env keys: [`config/VERCEL_ENV_KEYS.md`](config/VERCEL_ENV_KEYS.md). |
| 78 | +Apply `supabase/migrations/` in order before testing auth or storage. |
121 | 79 |
|
122 | 80 | ```bash |
123 | | -pnpm verify # lint · types · unit · contracts · no-auto-send · build |
124 | | -pnpm test:e2e:smoke # Playwright smoke (desktop + mobile) |
125 | | -pnpm test:e2e # full browser suite |
| 81 | +pnpm verify # lint · types · unit · contracts · no-auto-send · build |
| 82 | +pnpm test:e2e:smoke # Playwright smoke |
126 | 83 | ``` |
127 | 84 |
|
128 | | -CI runs the same deterministic checks on every PR and on pushes to `main`, plus secret scanning and smoke tests. Do not treat a green badge as a substitute for reading the safety contract. |
129 | | - |
130 | | -Health (JSON, no auth): `GET https://www.unhold.live/healthz` or `/api/v1/health`. |
131 | | - |
132 | 85 | --- |
133 | 86 |
|
134 | | -## Repository layout |
| 87 | +## Layout |
135 | 88 |
|
136 | 89 | ```text |
137 | | -app/ App Router UI + versioned /api/v1 routes |
138 | | -components/ Mobile-first case UI |
139 | | -lib/agents/ Intake, verifier, drafter, evidence, monitor, escalator |
140 | | -lib/api/ Authz, errors, request contracts |
141 | | -lib/escalations/ Deterministic proof gates |
142 | | -lib/jobs/ Enqueue, claim, process, on-demand kick |
143 | | -lib/evidence/ Hash, mime, compress, vision prep |
144 | | -supabase/migrations/ Schema, RLS, storage, privileges |
145 | | -tests/unit/ Domain + golden fixtures |
146 | | -tests/contract/ API and migration contracts |
147 | | -tests/e2e/ Journeys + a11y smoke |
148 | | -docs/ Product safety, deploy, research notes |
| 90 | +app/ UI + /api/v1 |
| 91 | +components/ Case UI |
| 92 | +lib/ Domain, jobs, evidence, gates |
| 93 | +supabase/migrations/ Schema and policies |
| 94 | +tests/ unit · contract · e2e |
| 95 | +docs/ Product safety, deploy |
| 96 | +messages/ en + hi copy |
149 | 97 | ``` |
150 | 98 |
|
151 | 99 | --- |
152 | 100 |
|
153 | | -## Status and limitations |
| 101 | +## Limits (honest) |
154 | 102 |
|
155 | | -Public beta **0.1.0**. Expect sharp edges; report them via issues or [security advisories](SECURITY.md). |
| 103 | +Public beta **0.1.0**. |
156 | 104 |
|
157 | | -- The user must verify extracted fields and send every letter themselves. |
| 105 | +- You verify extracted fields and send every letter. |
158 | 106 | - Process guidance can go stale; dated sources are not personal legal advice. |
159 | | -- Model reads can be wrong; low-confidence or inconsistent files are flagged for review. |
160 | | -- Whether an account moves, and when, is decided only by the bank and the ordering authority. |
| 107 | +- Document reads can be wrong; low-confidence files are flagged. |
| 108 | +- Only the bank and ordering authority decide if an account moves. |
161 | 109 |
|
162 | 110 | --- |
163 | 111 |
|
164 | | -## Security and contributing |
| 112 | +## Security · contributing |
165 | 113 |
|
166 | | -- Vulnerabilities: private report only — [`SECURITY.md`](SECURITY.md). |
167 | | -- Changes: [`CONTRIBUTING.md`](CONTRIBUTING.md). Prefer safer, clearer, more reliable behaviour over new claims. |
168 | | - |
169 | | ---- |
| 114 | +- Vulnerabilities: [`SECURITY.md`](SECURITY.md) (private report). |
| 115 | +- Patches: [`CONTRIBUTING.md`](CONTRIBUTING.md). Prefer safer and clearer over louder claims. |
170 | 116 |
|
171 | | -MIT License · Maintained by [thribhuvan003](https://github.com/thribhuvan003) |
| 117 | +MIT · [thribhuvan003](https://github.com/thribhuvan003) |
0 commit comments