# 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 |