Spaces:
Sleeping
Sleeping
| # 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 | | |