feat(quotas): per-workspace soft caps + AI recovery rule
Soft caps on the two resources a bad-actor signup could pump fastest:
- 3 active projects per workspace
- 3 active (running/provisioning) dev containers per workspace
Suspended dev containers don't count (they're free), so a power
user can have many projects with most containers idle. Limits are
overridable via env vars (VIBN_QUOTA_MAX_*) for a global lift.
Hits surface as HTTP 402 with structured payload {error, code,
current, limit}. AI's error-recovery middleware matches the
QUOTA_EXCEEDED code and synthesizes guidance: tell the user which
cap was hit, offer to suspend something or contact support, do NOT
retry blindly.
Wired:
- lib/quotas.ts — assertProjectQuota,
assertDevContainerQuota,
getQuotaStatus
- app/api/projects/create/route.ts — checks before create
- lib/dev-container.ts — checks before resume +
net-new ensure
- app/api/mcp/route.ts — devcontainer.ensure
translates QuotaExceededError
to 402
- lib/ai/error-recovery.ts — workspace-quota-exceeded rule
Closes BETA_LAUNCH_PLAN.md task 4.6.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { createRepo, createWebhook, getRepo, listWebhooks, GITEA_ADMIN_USER_EXPO
|
||||
import { getOrCreateProvisionedWorkspace } from '@/lib/workspaces';
|
||||
import { ensureProjectCoolifyProject } from '@/lib/projects';
|
||||
import { ensureSentryProject } from '@/lib/integrations/sentry';
|
||||
import { assertProjectQuota, QuotaExceededError } from '@/lib/quotas';
|
||||
import { loadGithubIntegration } from '@/lib/integrations/github';
|
||||
import type { ProjectPhaseData, ProjectPhaseScores } from '@/lib/types/project-artifacts';
|
||||
|
||||
@@ -91,6 +92,20 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: 'Project slug already exists' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Per-workspace project quota — soft cap at VIBN_QUOTA_MAX_PROJECTS_PER_WORKSPACE
|
||||
// (default 3) to bound runaway resource cost from bad-actor signups.
|
||||
try {
|
||||
await assertProjectQuota({ id: vibnWorkspace.id, slug: workspace });
|
||||
} catch (qe) {
|
||||
if (qe instanceof QuotaExceededError) {
|
||||
return NextResponse.json(
|
||||
{ error: qe.message, code: qe.code, current: qe.current, limit: qe.limit },
|
||||
{ status: 402 }, // 402 Payment Required — semantically "you've hit a tier limit".
|
||||
);
|
||||
}
|
||||
throw qe;
|
||||
}
|
||||
|
||||
const projectId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user