S1: Four gate fixes on deal rooms and co-invest. Decision D614. No migration. Found by #1165's audit (area 6).
What is true on main
- Join.
POST /api/deals/:id/dealroom/join (cloudflare-worker/src/routes/deals.ts) inserts a membership without checking that the deal exists, so a bad id still uses up a quota slot.
- Leave. Every
/api/deals/* route sits behind requireInvestorTier('professional') (cloudflare-worker/src/index.ts, INVESTOR_PRO_PREFIXES). An investor who drops to Free cannot call DELETE /api/deals/:id/dealroom/leave for the rooms they joined.
- Co-invest.
GET /api/funds/syndication (routes/funds.ts) checks only the role. The pricing page (frontend/src/pages/InvestorPricingPage.jsx) sells "Co-invest discovery" as Institutional only, yet any investor reads every listing.
- Create.
POST /api/deals takes any status. A status the table's CHECK does not allow becomes a 500.
The work
- Join answers 404
deal_not_found for a deal that does not exist, and writes nothing.
- Leave is reachable on any plan: exempt that one route from the Professional gate. The rest of
/api/deals/* stays gated.
- Co-invest requires Institutional for investors. Use the same middleware and paywall shape (
required: 'institutional'), so the client's paywall opens. Admins and partners pass, as today.
- Create refuses an unknown
status with a 400 that lists the allowed ones, read from the same list the table allows.
- D614 records the four gates.
Files this task owns
cloudflare-worker/src/routes/deals.ts (join and create only)
cloudflare-worker/src/index.ts (the investor gate's mounts only)
cloudflare-worker/src/routes/funds.ts (the syndication route only)
- new Worker tests
documentation/architecture/decisions/D614.md
Done when
- Tests cover:
- join refused for an unknown deal, using no quota;
- a Free investor can leave a room but still cannot list deals;
- co-invest answers 402 with
required: 'institutional' for a Professional investor, and 200 for an Institutional investor, an admin and a partner;
- create refuses a bad status with a 400.
- Every new assertion is mutation-checked.
Generated by Claude Code
S1: Four gate fixes on deal rooms and co-invest. Decision D614. No migration. Found by #1165's audit (area 6).
What is true on
mainPOST /api/deals/:id/dealroom/join(cloudflare-worker/src/routes/deals.ts) inserts a membership without checking that the deal exists, so a bad id still uses up a quota slot./api/deals/*route sits behindrequireInvestorTier('professional')(cloudflare-worker/src/index.ts,INVESTOR_PRO_PREFIXES). An investor who drops to Free cannot callDELETE /api/deals/:id/dealroom/leavefor the rooms they joined.GET /api/funds/syndication(routes/funds.ts) checks only the role. The pricing page (frontend/src/pages/InvestorPricingPage.jsx) sells "Co-invest discovery" as Institutional only, yet any investor reads every listing.POST /api/dealstakes anystatus. A status the table's CHECK does not allow becomes a 500.The work
deal_not_foundfor a deal that does not exist, and writes nothing./api/deals/*stays gated.required: 'institutional'), so the client's paywall opens. Admins and partners pass, as today.statuswith a 400 that lists the allowed ones, read from the same list the table allows.Files this task owns
cloudflare-worker/src/routes/deals.ts(join and create only)cloudflare-worker/src/index.ts(the investor gate's mounts only)cloudflare-worker/src/routes/funds.ts(the syndication route only)documentation/architecture/decisions/D614.mdDone when
required: 'institutional'for a Professional investor, and 200 for an Institutional investor, an admin and a partner;Generated by Claude Code