courtmitra / GAP_AUDIT_M1_M9.md
Hetansh Waghela
feat: Full Clerk Google OAuth + user profiles + combined API/Worker deploy
f69e867
|
Raw
History Blame Contribute Delete
11.2 kB
# CourtMitra M1-M9 Gap Audit
## Audit Date: 2026-05-29
## Commit Under Review: f892836 "Implement CourtMitra autopilot roadmap"
---
### M0 β€” Core Automation Loop
**Status:** Mostly complete. Intake, evidence, facts, legal grounding, route plan, action preview/consent, email send, reply ingestion, follow-up, resolution, ledger all have code paths. Tests green.
---
### M1 β€” Rate-Limit & Action Executor Idempotency
**Current:**
- `checkRateLimit` lives in `action-execution-policy.ts` and is called from `executeAction`.
- Source is `submissions` table with `status = "sent"` in a rolling window.
**Gaps / Bugs:**
1. **Single source of truth risk.** The comment says "counted from `submissions`/`action_attempts`", but only `submissions` is queried. If the post-send DB transaction rolls back, the email was sent but `submissions` row is missing β†’ under-count on retry.
2. **Idempotency gap.** `canExecute` allows `status === "executing"`. If the DB transaction fails after `emailAdapter.submit` succeeds, Inngest retries and the email is sent again because nothing blocks a second execution of an "executing" action.
3. **Env vars undocumented.** `ACTION_RATE_WINDOW_MINUTES` and `ACTION_RATE_MAX_PER_WINDOW` are not in `HANDOFF.md`.
**Fix plan:**
- Count from `action_attempts` (status = "succeeded") as a fallback / dual source.
- In `executeAction`, query existing attempts before calling the adapter. If a non-failed attempt exists and the prior attempt has a `startedAt` older than a short threshold, assume send succeeded and reconcile DB state without re-sending.
---
### M2 β€” Escalation Deadline Timezone
**Current:**
- `deadline-checker` cron runs hourly.
- `escalation-policy.ts` `addDays()` uses `new Date(from.getTime() + days * 24 * 60 * 60 * 1000)`.
- `action-executor.ts` sets `currentDeadlineAt: addDays(new Date(), deadlineDays)`.
**Gaps / Bugs:**
1. **Timezone not Asia/Kolkata.** `addDays` operates on server-local UTC time. If the server is not in IST, deadlines drift by 5.5 hours, causing the hourly checker to fire too early or too late.
2. **State rank mismatch.** `resolution-verify.ts` uses its own `STATE_RANK` map with different numeric values (`resolution_claimed: 17`, `resolved: 18`) compared to the canonical map in other workers (`resolution_claimed: 18`, `resolved: 19`). This can cause state regression or incorrect transitions.
**Fix plan:**
- Create a `deadlineInKolkata(from, days)` helper that computes the deadline in `Asia/Kolkata` timezone.
- Align `resolution-verify.ts` STATE_RANK with the canonical map used by `route-plan.ts`, `reply-classify.ts`, etc.
---
### M3 β€” Autopilot Triggers & Resolution Flow
**Current:**
- `autopilot-tick` Inngest function listens to `mission.autopilot_tick`.
- Emitted by `route-plan.ts` (after plan ready) and `reply-classify.ts` (after reply classified).
- `runAutopilotTick` uses a ToolLoopAgent with tools: `halt`, `draftFirstAction`, `openParallelTracks`, `requestResolution`, `waitForUser`.
**Gaps / Bugs:**
1. **Auto-claims resolution without user consent.** The `requestResolution` tool creates a `resolutionOutcomes` row, sets mission state to `resolution_claimed`, and emits `mission.resolution_claimed` β€” all without explicit user approval. Phase 1 spec requires user-confirmed resolution.
2. **Missing triggers.** Autopilot does NOT tick on:
- `mission.deadline_breached` (should evaluate next step)
- `action_executed` / `action_execution_failed` (should decide follow-up)
- `followup.due` (should draft escalation)
3. **No mode-gated behavior.** `autopilotMode` on the mission row is read but never used. The `requestResolution` tool should be blocked in "moderate" mode.
**Fix plan:**
- Change `requestResolution` to emit a `resolution.proposed` snapshot and wait for user confirmation, instead of auto-claiming.
- Add autopilot tick emission in `escalation.ts` (deadline breached) and after action execution.
- Respect `autopilotMode`: only auto-claim resolution in an explicit "aggressive" mode; "moderate" should always wait for user.
---
### M4 β€” Parallel Tracks (routeGraph AND/OR, parent_action_id)
**Current:**
- Schema has `actions.parentActionId` and `actions.trackLabel`.
- `draftConsentGatedAction` accepts `parentActionId` and `trackLabel`.
- `openParallelTracks` tool in autopilot.ts drafts up to 2 actions with different authorities.
- `resolution-verify.ts` supersedes dangling `proposed`/`preview_ready`/`approved` actions on resolution.
**Gaps / Bugs:**
1. **`parentActionId` never set.** In `openParallelTracks`, each track action is drafted with `trackLabel` but `parentActionId` is always `null`. There is no "parent" action that spawned the parallel tracks, so the link is unused.
2. **Route plan has no AND/OR encoding.** The generated `RoutePlanStepDto[]` is linear. The `routeGraph` in route_rules has `nodes`/`edges`, but the LLM-generated plan steps do not encode which steps can run in parallel.
3. **Escalation policy ignores tracks.** `getNextEscalationTier` advances linearly; it doesn't respect track boundaries. If two parallel tracks are open, escalating one should not automatically advance the other.
**Fix plan:**
- When `openParallelTracks` opens tracks, create a synthetic "parent" action (or use the first action) and set `parentActionId` on subsequent tracks.
- Add `parallelGroup` field to `RoutePlanStepDto` so the plan can encode parallelizable steps.
- Update `getNextEscalationTier` to accept an optional `trackLabel` filter so each track escalates independently.
---
### M5 β€” Settlement Flow
**Current:**
- `reply-classify.v2` prompt extracts settlement offers.
- `decideSettlementNextAction` in `settlement-policy.ts` recommends accept/counter/escalate/wait.
- `reply-classify.ts` auto-drafts a counter-offer action if policy says "counter".
**Gaps / Bugs:**
1. **No settlement acceptance action.** If the user wants to accept a settlement, there's no UI action or API endpoint to generate an acceptance email.
2. **No integration tests for settlement end-to-end.** The unit tests for `settlement-policy.ts` exist, but there's no test covering the reply-classify β†’ settlement policy β†’ draft action flow.
3. **Counter-offer authority may be wrong.** `from` (the raw reply email) is used as the authority/destination, but it could be a no-reply address.
**Fix plan:**
- Add a settlement-accept endpoint stub and UI button.
- Add an integration test for the settlement classification β†’ counter-offer flow.
- Document the counter-offer authority caveat.
---
### M6 β€” RTI Seeds & Prompts
**Current:**
- `legal-data.ts` has RTI Act 2005 chunks (Sections 6, 7, 19, 19(6)).
- `route-rules-data.ts` has `governmentRtiRouteRule` with RTI0β†’RTI1β†’RTI2 steps.
- Schema supports `issueDomain = "government"`.
**Gaps / Bugs:**
1. **Intake prompt missing government domain.** `intake.v1` prompt only lists `"consumer" | "payment" | "cyber_fraud"`, so LLM will never classify a government/RTI grievance correctly.
2. **No RTI-specific prompt.** There is no dedicated `rti-application.v1` or `rti-packet.v1` prompt for generating RTI application text.
3. **No E2E test.** The test suite has no fixture for a government-domain mission.
**Fix plan:**
- Update `intake.v1` prompt to include `"government"` domain.
- Add `rti-packet.v1` prompt stub.
- Add a government mission fixture to the seed/integration test data.
---
### M7 β€” Voice / Indic Intake
**Current:**
- `evidence.ts` routes audio to ASR adapter, stores transcript in `transcripts` table.
- `transcripts` schema exists (Module 2 migration).
- Redacted text is fed into claim extraction.
**Gaps / Bugs:**
1. **Original transcript lost.** `evidence.ts` passes `redactedText` as `rawText` to the claim extraction flow. The original unredacted transcript is stored in `transcripts.text` but never preserved separately for audit.
2. **Missing unique constraint / idempotency.** `db.insert(schema.transcripts).values(...).onConflictDoNothing()` has no explicit target constraint. On Inngest retry, duplicate rows could accumulate if the table has no unique index on `evidenceItemId`.
3. **No language persistence for intake.** ASR returns `language`, but it's only stored in `transcripts.language`. It's not propagated to the mission's `facts` or used to trigger translation.
**Fix plan:**
- Store both `originalText` and `redactedText` in the ASR result / evidence flow.
- Add a unique index on `transcripts(evidenceItemId)` (or ensure one exists) and specify the conflict target.
- Propagate detected language to mission facts.
---
### M8 β€” Portal Assist
**Current:**
- `portal-assist.ts` contracts define `PortalAssistState`, `PortalAssistSession`, etc.
- Schema has `portal_assist_sessions` table.
- No Inngest function, no API controller, no UI panel.
**Gaps / Bugs:**
1. **No web session viewer.** The mission page has no portal-assist panel.
2. **No screenshot/receipt flow.** There is no endpoint to upload a receipt after a portal submission.
3. **No honest `receipt_verified` path.** The `ProofState` enum includes `receipt_verified`, but no code path sets it. Portal actions are always `ready_for_user_submission` or `user_confirmed_external_submission`.
**Fix plan:**
- Add `POST /api/missions/:id/portal-receipt` endpoint that accepts a receipt file and updates the action's `proofState` to `receipt_verified`.
- Add a minimal portal-assist panel in the mission UI.
- Document that portal automation is manual-packet only until Browserbase/Stagehand is configured.
---
### M9 β€” Swarm / Social / DPDP
**Current:**
- Swarm and social contracts exist (`swarm.ts`, `social.ts`).
- Schema has `entities`, `swarms`, `swarm_members`, `social_accounts`, `social_posts` tables.
- `copy-ready-fallback.ts` social adapter exists.
- DPDP controller exists in API.
**Gaps / Bugs:**
1. **No swarm UI on mission page.** `SwarmWithEntity` is imported but never rendered.
2. **Social publisher not consent-gated.** `social.ts` contract has `consentGrantId`, but there's no check in the adapter or API that a social post has a valid consent grant before publishing.
3. **DPDP consent flow incomplete.** No evidence that the first-contact consent notice is actually delivered to WhatsApp/Telegram users.
**Fix plan:**
- Add a swarm membership opt-in stub to the mission UI.
- Add a `canPublishSocial` guard that validates `consentGrantId` + action preview hash before any social adapter call.
- Document DPDP first-contact delivery in the intake flow.
---
## Summary of Priority Fixes
| Priority | Module | Fix |
|----------|--------|-----|
| P0 | M1 | Dual-source rate limit (submissions + attempts); executor attempt guard |
| P0 | M2 | Asia/Kolkata deadline helper; align STATE_RANK |
| P0 | M3 | Stop autopilot auto-claiming resolution; add missing tick triggers |
| P1 | M4 | Set parentActionId in openParallelTracks; add parallelGroup to steps |
| P1 | M6 | Add "government" to intake.v1 prompt |
| P1 | M7 | Preserve original transcript; fix transcript conflict target |
| P1 | M8 | Add receipt_verified endpoint stub |
| P1 | M9 | Add social publish consent guard |
| P2 | M5 | Settlement integration test |