--- issue: "003" title: "Route shadowing: two dispute routers mounted at /api/disputes cause non-deterministic handler dispatch" severity: critical domain: dispute labels: [backend, bug] status: open created: 2026-05-29 source: Doc vs Code Audit 2026-05-29 --- # 🔴 Route shadowing: two dispute routers mounted at /api/disputes cause non-deterministic handler dispatch **Severity:** critical **Domain:** dispute **Labels:** backend, bug ## Description In `backend/src/app.ts`, two separate dispute routers are mounted on the same path `/api/disputes`: - Line ~521: `dashboardDisputeRoutes` (first — unguarded `POST /:id/resolve`, `PATCH /:id/status`) - Line ~585: `releaseHold disputeRoutes` (second — admin-guarded `POST /:purchaseRequestId/resolve`, also `GET /:purchaseRequestId/status`) Express evaluates in registration order. A `POST /api/disputes/{purchaseRequestId}/resolve` request will match the **dashboard router's** `POST /:id/resolve` handler first (since `:id` and `:purchaseRequestId` are identical route patterns). This executes the unguarded Dispute CRUD resolve instead of the admin-guarded escrow release-hold logic. ## Current Behavior `POST /api/disputes/{purchaseRequestId}/resolve` executes the dashboard `resolveDispute` controller (updates the Dispute document only, no role guard) rather than the intended `releaseHold` handler (admin-only, clears escrow). ## Expected Behavior The escrow-release resolve handler should be reachable at a distinct, unambiguous path (e.g., `/api/disputes/hold/:purchaseRequestId/resolve` or mounted at a different prefix). ## Affected Files - `backend/src/app.ts` — two `app.use('/api/disputes', ...)` mount points ## References - [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C18 - Related: [[ISSUE-002-dispute-resolve-no-role-guard]]