Hetansh Waghela commited on
Commit
ad76a88
Β·
1 Parent(s): 7aa8d9d

handoff doc

Browse files
Files changed (2) hide show
  1. HANDOFF.md +494 -0
  2. infra/.env.example +5 -0
HANDOFF.md ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CourtMitra MissionOS β€” Handoff Document
2
+
3
+ ## 1. What This Is
4
+
5
+ CourtMitra is a citizen-side grievance/legal-action autopilot for India. A user describes a problem once (WhatsApp / Telegram / browser); the system turns it into a **mission**, builds the evidence file, grounds it in Indian law/procedure, picks an official route, drafts + sends approved actions, follows up, and records the outcome.
6
+
7
+ ### Architecture
8
+
9
+ ```
10
+ apps/
11
+ web/ β†’ Next.js App Router (port 3000)
12
+ api/ β†’ NestJS + Fastify (port 3001)
13
+ worker/ β†’ Inngest functions (port 3002)
14
+ packages/
15
+ domain/ β†’ Pure business logic (no framework imports)
16
+ ports/ β†’ Interfaces only (hexagonal boundary)
17
+ adapters/ β†’ Concrete implementations (LLM, email, channels, etc.)
18
+ db/ β†’ Drizzle schema, migrations, seeds
19
+ contracts/ β†’ Zod schemas (single source of truth)
20
+ prompts/ β†’ Versioned prompt templates
21
+ ui/ β†’ Shared React components
22
+ infra/
23
+ docker-compose.yml β†’ Postgres, Inngest dev server, API, Worker, Web
24
+ Dockerfile.* β†’ Multi-stage builds
25
+ ```
26
+
27
+ ### How It Works
28
+
29
+ 1. **Intake**: User sends a message via WhatsApp/Telegram/browser β†’ system extracts structured facts via LLM
30
+ 2. **Evidence**: User uploads screenshots/PDFs/audio β†’ vision-LLM or ASR extracts data
31
+ 3. **Facts**: User confirms extracted facts
32
+ 4. **Legal grounding**: pgvector + FTS retrieves relevant Indian law citations
33
+ 5. **Route plan**: LLM generates a step-by-step plan grounded in law
34
+ 6. **Action preview + consent**: System drafts an action, user approves with hash-bound consent
35
+ 7. **Execution**: Resend sends email, portal fills forms, etc.
36
+ 8. **Follow-up**: System tracks replies, schedules reminders
37
+ 9. **Resolution**: Outcome recorded, anonymized ledger entry
38
+
39
+ ---
40
+
41
+ ## 2. Environment Variables β€” Complete Reference
42
+
43
+ Copy `infra/.env.example` to `infra/.env` and fill in the values below.
44
+
45
+ ### Required (system won't start without these)
46
+
47
+ | Variable | Where to Get | Notes |
48
+ |----------|-------------|-------|
49
+ | `DATABASE_URL` | Auto-set by Docker compose | `postgres://courtmitra:courtmitra@localhost:5432/courtmitra` for local dev |
50
+ | `DIRECT_DATABASE_URL` | Same as DATABASE_URL | Used by Drizzle for direct connections |
51
+ | `AI_PROVIDER` | Choose one | `openrouter` (default) or `opencode` |
52
+ | `OPENROUTER_API_KEY` | https://openrouter.ai/keys | Free tier available. Used for LLM calls. |
53
+ | `OPENAI_API_KEY` | https://platform.openai.com/api-keys | Required for Whisper ASR (voice notes). Free tier: $5 credit. |
54
+
55
+ ### Required for Email Sending
56
+
57
+ | Variable | Where to Get | Notes |
58
+ |----------|-------------|-------|
59
+ | `RESEND_API_KEY` | https://resend.com/api-keys | Free tier: 100 emails/day, 3000/month |
60
+ | `RESEND_FROM` | Set in Resend dashboard | e.g. `noreply@yourdomain.com` |
61
+ | `RESEND_REPLY_DOMAIN` | Your domain | e.g. `reply.yourdomain.com` β€” used for reply-token threading |
62
+
63
+ ### Required for Vector Search
64
+
65
+ | Variable | Where to Get | Notes |
66
+ |----------|-------------|-------|
67
+ | `QDRANT_URL` | https://cloud.qdrant.io | Free tier: 1GB storage, 1 cluster |
68
+ | `QDRANT_API_KEY` | Qdrant Cloud dashboard | Created when you set up the cluster |
69
+ | `QDRANT_COLLECTION` | Just set | `courtmitra_legal` (default) |
70
+
71
+ ### Optional β€” WhatsApp
72
+
73
+ | Variable | Where to Get | Notes |
74
+ |----------|-------------|-------|
75
+ | `WHATSAPP_VERIFY_TOKEN` | You create this | Any random string for webhook verification |
76
+ | `WHATSAPP_APP_SECRET` | Meta Developer Portal | For HMAC signature verification |
77
+ | `WHATSAPP_ACCESS_TOKEN` | Meta Developer Portal | Permanent token from App Dashboard |
78
+ | `WHATSAPP_PHONE_NUMBER_ID` | Meta Developer Portal | Found in WhatsApp > API Setup |
79
+ | `WHATSAPP_BUSINESS_ACCOUNT_ID` | Meta Developer Portal | Found in WhatsApp > API Setup |
80
+
81
+ ### Optional β€” Telegram
82
+
83
+ | Variable | Where to Get | Notes |
84
+ |----------|-------------|-------|
85
+ | `TELEGRAM_BOT_TOKEN` | @BotFather on Telegram | `/newbot` command |
86
+
87
+ ### Optional β€” Portal Automation
88
+
89
+ | Variable | Where to Get | Notes |
90
+ |----------|-------------|-------|
91
+ | `BROWSERBASE_API_KEY` | https://www.browserbase.com | Free tier: 5 hours/month |
92
+ | `BROWSERBASE_PROJECT_ID` | Browserbase dashboard | Created when you sign up |
93
+ | `ENABLE_PORTAL_AUTOMATION` | Set to `1` | Without this, portal assist degrades to manual packet |
94
+
95
+ ### Optional β€” eCourts CNR Lookup
96
+
97
+ | Variable | Where to Get | Notes |
98
+ |----------|-------------|-------|
99
+ | `SUREPASS_API_KEY` | https://surepass.io | Paid API for Indian court case data |
100
+
101
+ ### Optional β€” Observability
102
+
103
+ | Variable | Where to Get | Notes |
104
+ |----------|-------------|-------|
105
+ | `LANGFUSE_PUBLIC_KEY` | https://langfuse.com | Free tier available |
106
+ | `LANGFUSE_SECRET_KEY` | Langfuse dashboard | |
107
+
108
+ ### Defaults (usually fine as-is)
109
+
110
+ | Variable | Default | Notes |
111
+ |----------|---------|-------|
112
+ | `APP_BASE_URL` | `http://localhost:3000` | Web app URL |
113
+ | `API_BASE_URL` | `http://localhost:3001` | API URL |
114
+ | `WEBHOOK_BASE_URL` | `http://localhost:3001` | For Resend/WhatsApp webhook callbacks |
115
+ | `LOCAL_STORAGE_DIR` | `.storage` | File storage path (relative to repo root) |
116
+ | `INNGEST_BASE_URL` | `http://localhost:8288` | Inngest dev server |
117
+ | `INNGEST_DEV` | `1` | Enable Inngest dev mode |
118
+ | `PII_HASH_SALT` | `dev-salt-change-me` | For PII hashing |
119
+ | `JWT_SECRET` | `dev-jwt-secret-change-me` | For session tokens |
120
+ | `AI_MODEL` | `google/gemini-2.0-flash-exp:free` | LLM model (free on OpenRouter) |
121
+ | `AI_VISION_MODEL` | `google/gemini-2.0-flash-exp:free` | Vision model for evidence parsing |
122
+
123
+ ---
124
+
125
+ ## 3. Step-by-Step Setup
126
+
127
+ ### Prerequisites
128
+
129
+ - Node.js >= 20
130
+ - pnpm 10.20.0+ (`corepack enable && corepack prepare pnpm@10 --activate`)
131
+ - Docker + Docker Compose
132
+ - Git
133
+
134
+ ### Step 1: Clone and Install
135
+
136
+ ```bash
137
+ git clone <repo-url> hackathon
138
+ cd hackathon
139
+ pnpm install
140
+ ```
141
+
142
+ ### Step 2: Start Infrastructure
143
+
144
+ ```bash
145
+ # Start Postgres + Inngest dev server
146
+ pnpm infra:up
147
+
148
+ # Wait for Postgres to be healthy (check with:)
149
+ docker ps
150
+ # Should show courtmitra_pg as "healthy"
151
+ ```
152
+
153
+ ### Step 3: Set Up Environment
154
+
155
+ ```bash
156
+ cp infra/.env.example infra/.env
157
+ # Edit infra/.env with your API keys (see table above)
158
+ ```
159
+
160
+ **Minimum viable .env for local dev:**
161
+ ```bash
162
+ DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra
163
+ DIRECT_DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra
164
+ AI_PROVIDER=openrouter
165
+ OPENROUTER_API_KEY=<your-key>
166
+ OPENAI_API_KEY=<your-key>
167
+ RESEND_API_KEY=<your-key>
168
+ RESEND_FROM=noreply@yourdomain.com
169
+ RESEND_REPLY_DOMAIN=reply.yourdomain.com
170
+ QDRANT_URL=<your-qdrant-cloud-url>
171
+ QDRANT_API_KEY=<your-qdrant-key>
172
+ QDRANT_COLLECTION=courtmitra_legal
173
+ LOCAL_STORAGE_DIR=.storage
174
+ INNGEST_BASE_URL=http://localhost:8288
175
+ INNGEST_DEV=1
176
+ PII_HASH_SALT=dev-salt-change-me
177
+ JWT_SECRET=dev-jwt-secret-change-me
178
+ AI_MODEL=google/gemini-2.0-flash-exp:free
179
+ AI_VISION_MODEL=google/gemini-2.0-flash-exp:free
180
+ ```
181
+
182
+ ### Step 4: Run Database Migrations
183
+
184
+ ```bash
185
+ pnpm db:migrate
186
+ ```
187
+
188
+ This creates all 30+ tables including: users, missions, actions, evidence, legal_chunks, followups, social_posts, case_lookups, portal_assist_sessions, etc.
189
+
190
+ ### Step 5: Seed Legal Data + Qdrant
191
+
192
+ ```bash
193
+ # Seed legal sources, chunks, and route rules into Postgres
194
+ pnpm db:seed
195
+
196
+ # Seed Qdrant with embeddings (requires OPENROUTER_API_KEY for embeddings)
197
+ npx tsx scripts/seed-qdrant.ts
198
+ ```
199
+
200
+ ### Step 6: Start the Application
201
+
202
+ ```bash
203
+ # Start all three services (API, Worker, Web)
204
+ pnpm dev
205
+ ```
206
+
207
+ This starts:
208
+ - **Web** on http://localhost:3000
209
+ - **API** on http://localhost:3001
210
+ - **Worker** on http://localhost:3002
211
+ - **Inngest Dev Server** on http://localhost:8288 (from Docker)
212
+
213
+ ### Step 7: Verify Everything Works
214
+
215
+ ```bash
216
+ # Run all checks
217
+ pnpm typecheck && pnpm lint && pnpm test && pnpm test:contracts
218
+
219
+ # Check API health
220
+ curl http://localhost:3001/api/admin/health
221
+
222
+ # Check capabilities (shows what's available/blocked)
223
+ curl http://localhost:3001/api/capabilities
224
+ ```
225
+
226
+ ---
227
+
228
+ ## 4. How to Test the Product End-to-End
229
+
230
+ ### Test 1: Create a Mission from Browser
231
+
232
+ 1. Open http://localhost:3000
233
+ 2. Click "Create Mission"
234
+ 3. Select issue domain (consumer/payment/cyber_fraud)
235
+ 4. Enter title and description
236
+ 5. Click create β†’ mission appears on the board
237
+
238
+ ### Test 2: Send a Telegram Message (Easiest Channel Test)
239
+
240
+ 1. Set `TELEGRAM_BOT_TOKEN` in `.env`
241
+ 2. Restart the API
242
+ 3. Send a message to your bot on Telegram
243
+ 4. Check http://localhost:3000 β€” mission should appear
244
+ 5. Check Inngest dev server at http://localhost:8288 β€” events should flow
245
+
246
+ ### Test 3: Full Loop (Email Send)
247
+
248
+ 1. Create a mission from browser
249
+ 2. Confirm facts (click "Confirm Facts" in the mission room)
250
+ 3. Wait for route plan generation (check Inngest)
251
+ 4. Preview an action β†’ approve with consent
252
+ 5. Execute β†’ email sent via Resend
253
+ 6. Check proof_state = `sent_by_email`
254
+ 7. Reply to the email β†’ reply ingested β†’ mission advances
255
+
256
+ ### Test 4: WhatsApp (Requires Meta App)
257
+
258
+ 1. Set WhatsApp env vars
259
+ 2. Configure webhook URL in Meta Developer Portal: `https://your-domain/webhooks/whatsapp`
260
+ 3. Set verify token to match `WHATSAPP_VERIFY_TOKEN`
261
+ 4. Send a voice note or text message
262
+ 5. Mission created, evidence parsed, facts extracted
263
+
264
+ ### Test 5: Voice Notes (Requires OPENAI_API_KEY)
265
+
266
+ 1. Send a voice note via WhatsApp or Telegram
267
+ 2. System downloads audio β†’ transcribes via Whisper β†’ includes transcript in intake
268
+ 3. Check evidence_parsed_outputs for parserName = "whisper-asr"
269
+
270
+ ---
271
+
272
+ ## 5. What's Built (Phase 1 + Phase 2 Modules 1-8)
273
+
274
+ ### Phase 1 β€” Complete Automation Loop βœ…
275
+
276
+ | Component | Status |
277
+ |-----------|--------|
278
+ | Mission CRUD from browser | βœ… |
279
+ | WhatsApp webhook + media download | βœ… |
280
+ | Telegram webhook + voice | βœ… |
281
+ | Intake (LLM extracts structured facts) | βœ… |
282
+ | Vision-LLM evidence parsing | βœ… |
283
+ | Whisper ASR transcription | βœ… |
284
+ | Fact confirmation UI | βœ… |
285
+ | Legal grounding (pgvector + FTS + RRF) | βœ… |
286
+ | Route plan generation with citations | βœ… |
287
+ | Claim verification (literal substring) | βœ… |
288
+ | Action preview + consent hash | βœ… |
289
+ | Defamation gate | βœ… |
290
+ | PII redaction (Aadhaar/PAN/UPI/IFSC/phone/email) | βœ… |
291
+ | Email send via Resend | βœ… |
292
+ | Reply-token threading | βœ… |
293
+ | Reply classification | βœ… |
294
+ | Follow-up scheduling | βœ… |
295
+ | Resolution capture | βœ… |
296
+ | Public case page (private link) | βœ… |
297
+ | DPDP data export + erase | βœ… |
298
+ | Capability registry | βœ… |
299
+
300
+ ### Phase 2 β€” Modules 1-8 βœ…
301
+
302
+ | Module | What's Built |
303
+ |--------|-------------|
304
+ | **1. Complaint Swarm** | Entity resolution, swarm clustering, dossier generation, opt-in with consent |
305
+ | **2. ASR / Voice Notes** | Whisper API transcription, WhatsApp audio download, Telegram voice, PII redaction |
306
+ | **3. Indic Translation** | LLM-based language detection + translation, preferredLanguage on users, localized_texts table |
307
+ | **4. Hybrid RAG** | Qdrant Cloud adapter (dense vectors + Postgres FTS + RRF), seed script |
308
+ | **5. Portal Automation** | Playwright adapter, portal_assist_sessions, consent gate, receipt capture, degradation |
309
+ | **6. Company Resolver** | Organizations, resolver threads, domain verification, masked case view |
310
+ | **7. Social Publishing** | Social accounts/posts tables, copy-ready fallback, social card image generator |
311
+ | **8. eCourts / CNR** | Surepass API adapter, case_lookups table, hearing reminder scheduling |
312
+
313
+ ### Phase 2 β€” Modules 9-11 ❌ (Not Built)
314
+
315
+ | Module | What's Needed |
316
+ |--------|--------------|
317
+ | **9. Intermediary Compliance** | Takedown requests table, Grievance Officer contact, IT Rules 2021 timelines |
318
+ | **10. AI Public Account** | @CourtMitraWatch β€” requires modules 7+9, legal review |
319
+ | **11. Advanced Ledger** | Materialized views, route-success rates, repeat-offender clusters |
320
+
321
+ ---
322
+
323
+ ## 6. Known Issues / Limitations
324
+
325
+ ### Architecture
326
+ - **No auth system** β€” browser missions use a hardcoded "Demo User". No login, no session management.
327
+ - **Single-user demo** β€” all browser missions attach to the same demo user.
328
+ - **Local file storage** β€” evidence files stored on local filesystem (`.storage/`). Production needs R2/Supabase Storage.
329
+
330
+ ### Data
331
+ - **42 curated legal chunks** across 3 domains. No automated PDF ingestion (Docling skipped).
332
+ - **3 route rules** (consumer, payment, cyber_fraud). No employment/RTI/landlord domains.
333
+ - **All social posts are copy-ready** β€” no Meta App Review completed, no X API payment.
334
+
335
+ ### Code Quality
336
+ - **26 lint warnings** β€” mostly `any` types in existing code, unused imports.
337
+ - **No integration tests** β€” only unit tests with mocked DB.
338
+ - **Some `as never` casts** in Inngest event sends (type safety gap).
339
+
340
+ ### Docker Images
341
+ - `infra-api`: ~404MB
342
+ - `infra-worker`: ~384MB
343
+ - `infra-postgres`: ~640MB (pgvector base)
344
+
345
+ ---
346
+
347
+ ## 7. Key Commands Reference
348
+
349
+ ```bash
350
+ # Development
351
+ pnpm dev # Start all services (web + api + worker)
352
+ pnpm build # Build all packages
353
+ pnpm typecheck # TypeScript type checking
354
+ pnpm lint # ESLint
355
+ pnpm test # Run all tests
356
+ pnpm test:contracts # Run contract tests only
357
+
358
+ # Database
359
+ pnpm db:migrate # Run Drizzle migrations
360
+ pnpm db:seed # Seed legal data + route rules
361
+ npx tsx scripts/seed-qdrant.ts # Seed Qdrant with embeddings
362
+
363
+ # Docker
364
+ pnpm infra:up # Start Postgres + Inngest
365
+ pnpm infra:down # Stop all containers
366
+ docker compose -f infra/docker-compose.yml up -d # Same as above
367
+ docker compose -f infra/docker-compose.yml down # Same as above
368
+
369
+ # Verification (run before committing)
370
+ pnpm typecheck && pnpm lint && pnpm test && pnpm test:contracts
371
+ ```
372
+
373
+ ---
374
+
375
+ ## 8. File Structure (Key Files)
376
+
377
+ ```
378
+ CLAUDE.md # Project invariants β€” READ THIS FIRST
379
+ PHASE_1_automation_loop.md # Phase 1 spec + DoD
380
+ PHASE_2_scale_and_intelligence.md # Phase 2 spec (11 modules)
381
+
382
+ packages/
383
+ db/src/schema.ts # All 30+ table definitions
384
+ db/src/migrations/ # 0000 through 0009
385
+ db/src/seeds/ # legal-data.ts, route-rules-data.ts, run.ts
386
+ contracts/src/ # All Zod schemas (enums, mission, action, swarm, asr, etc.)
387
+ ports/src/index.ts # All port interfaces
388
+ adapters/src/ # All adapter implementations
389
+ legal/pgvector-fts.ts # Original Postgres RAG adapter
390
+ legal/qdrant-hybrid.ts # Qdrant Cloud hybrid adapter
391
+ audio/whisper-api.ts # Whisper ASR adapter
392
+ translation/llm-adapter.ts # LLM translation adapter
393
+ portal/playwright-adapter.ts # Playwright portal adapter
394
+ social/copy-ready-fallback.ts # Social publishing fallback
395
+ social/card-generator.ts # Social card image generator
396
+ case-lookup/surepass-adapter.ts # eCourts CNR adapter
397
+ channel/whatsapp.ts # WhatsApp Cloud API
398
+ channel/telegram.ts # Telegram Bot API
399
+ submission/resend-email.ts # Resend email adapter
400
+ redaction/regex.ts # PII regex redaction
401
+ redaction/regex-llm.ts # LLM-assisted redaction
402
+ storage/local-fs.ts # Local filesystem storage
403
+ index.ts # Adapter exports + capability registry
404
+
405
+ apps/
406
+ api/src/
407
+ app.module.ts # Root NestJS module (all modules wired)
408
+ workflow/inngest.module.ts # Inngest client + WorkflowService
409
+ actions/ # Action preview, consent, execute
410
+ missions/ # Mission CRUD + reply ingestion
411
+ evidence/ # Evidence files + fact confirmation
412
+ webhooks/ # WhatsApp, Telegram, email webhook handlers
413
+ conversations/ # Message ingestion service
414
+ route-plans/ # Route plan generation trigger
415
+ resolution/ # Resolution capture
416
+ ledger/ # Anonymized ledger
417
+ dpdp/ # Data export + erase
418
+ swarm/ # Complaint swarm
419
+ portal-assist/ # Portal automation
420
+ resolver/ # Company resolver
421
+ case-lookup/ # eCourts CNR lookup
422
+ capabilities/ # GET /api/capabilities
423
+ worker/src/
424
+ inngest.ts # All registered Inngest functions
425
+ deps.ts # Singleton dependencies
426
+ functions/
427
+ intake.ts # Run intake (extract facts)
428
+ evidence.ts # Parse evidence (vision-LLM or ASR)
429
+ route-plan.ts # Generate route plan with citations
430
+ action-execute.ts # Execute actions via ActionExecutor
431
+ followup-schedule.ts # Schedule follow-up reminders
432
+ followup-handler.ts # Handle due follow-ups
433
+ reply-classify.ts # Classify inbound replies
434
+ resolution-verify.ts # Verify resolution
435
+ swarm.ts # Entity resolution + clustering
436
+ portal-assist.ts # Portal automation flow
437
+ case-lookup.ts # eCourts CNR lookup
438
+ lib/
439
+ action-executor.ts # The ONLY place side-effects happen
440
+ web/src/
441
+ app/page.tsx # Mission board (list + create)
442
+ app/missions/[id]/page.tsx # Mission room (full detail)
443
+ app/p/[slug]/page.tsx # Public case page (private link)
444
+ app/ledger/page.tsx # Anonymized ledger dashboard
445
+ lib/api.ts # Typed API client
446
+ ```
447
+
448
+ ---
449
+
450
+ ## 9. Troubleshooting
451
+
452
+ ### "DATABASE_URL is not set"
453
+ Make sure `infra/.env` exists and has `DATABASE_URL` set. The API and worker read from this file.
454
+
455
+ ### "Qdrant connection failed"
456
+ Check `QDRANT_URL` and `QDRANT_API_KEY`. For local Qdrant: `docker run -p 6333:6333 qdrant/qdrant`.
457
+
458
+ ### "OpenAI API key not set" (Whisper)
459
+ Set `OPENAI_API_KEY` for voice note transcription. Without it, audio evidence will be skipped.
460
+
461
+ ### "Resend API key not set" (Email)
462
+ Set `RESEND_API_KEY` and `RESEND_FROM` for email sending. Without it, email actions will fail with `proof_state: failed`.
463
+
464
+ ### "Inngest function not found"
465
+ Make sure the Inngest dev server is running (`pnpm infra:up`) and the worker is running (`pnpm dev`). Check http://localhost:8288 for the Inngest dashboard.
466
+
467
+ ### Tests failing
468
+ ```bash
469
+ pnpm typecheck # Check for type errors first
470
+ pnpm test # Run tests
471
+ ```
472
+
473
+ ### Docker build failing
474
+ ```bash
475
+ # Rebuild from scratch
476
+ docker compose -f infra/docker-compose.yml down -v
477
+ docker compose -f infra/docker-compose.yml build --no-cache
478
+ docker compose -f infra/docker-compose.yml up -d
479
+ ```
480
+
481
+ ---
482
+
483
+ ## 10. Architecture Invariants (from CLAUDE.md)
484
+
485
+ These are non-negotiable. Violating them breaks the system's trustworthiness:
486
+
487
+ 1. **Hexagonal** β€” `packages/domain` has ZERO framework imports
488
+ 2. **Everything external is an Action** β€” side-effects only happen in `action-executor.ts`
489
+ 3. **No fake submissions** β€” `proof_state` must reflect reality
490
+ 4. **No legal claim without cited source** β€” literal substring check
491
+ 5. **No public output without redaction + consent** β€” Presidio redaction before share
492
+ 6. **Contracts-first** β€” all Zod schemas in `packages/contracts`
493
+ 7. **Idempotent writes** β€” every write endpoint accepts `Idempotency-Key`
494
+ 8. **Every schema change is a Drizzle migration** β€” no ad-hoc DB edits
infra/.env.example CHANGED
@@ -100,3 +100,8 @@ BROWSERBASE_PROJECT_ID=
100
  # --- Surepass (eCourts CNR lookup) ---
101
  # Paid third-party API for Indian court case data
102
  SUREPASS_API_KEY=
 
 
 
 
 
 
100
  # --- Surepass (eCourts CNR lookup) ---
101
  # Paid third-party API for Indian court case data
102
  SUREPASS_API_KEY=
103
+
104
+ # --- Portal automation (optional) ---
105
+ # Set to "1" to enable Playwright-based portal automation
106
+ # Without this, portal assist degrades to manual packet submission
107
+ ENABLE_PORTAL_AUTOMATION=