Files
vibn-agent-runner/vibn-frontend/app/api/admin/path-b/idle-sweep/route.ts
mawkone 6b8862ef2b feat(api): comprehensive QA hardening — security gates, chat improvements, beta scaffolds
Closes checklist items F-01..F-06, D-01..D-28, S-01..S-10, C-01..C-07,
B-01..B-07, R-01..R-02, O-03.

Security (28 deletions + 10 auth gates):
- Delete 28 unauthenticated debug/cursor/firebase/test routes
- Gate ai/chat, ai/conversation, context/summarize, work-completed with withTenantProject/withAuth
- Add HMAC-SHA256 signature verification to webhooks/coolify
- Switch all admin secret comparisons to timingSafeStringEq

Foundations (lib/server/*):
- api-handler.ts: withAuth, withTenantProject, withWorkspace, withAdminSecret, withRateLimit
- logger.ts: structured request-scoped logging with turnId
- audit-log.ts: writeAuditLog helper + audit_log table
- rate-limit.ts: Postgres sliding window rate limiter
- coolify-webhook.ts: verifyCoolifySignature
- timing-safe.ts: timingSafeStringEq

Chat hardening (chat/route.ts):
- MAX_TOOL_ROUNDS 15 → 8 (C-01)
- Loop detection: hard-break at 3 identical fingerprints (was 5) (C-02)
- Add 6-consecutive-tool-call hard-break (C-02)
- Mode: respond first, act second prompt block (C-03)
- SSE heartbeat every 25s via setInterval (C-04)
- Per-tool 45s timeout via Promise.race (C-05)
- turnId per-turn UUID for log correlation (C-06)
- Recovery fires when roundsSinceText >= 4 (C-07)
- SSE plan event on plan_task_add/edit (B-05)

Beta features:
- invites table + GET/POST /api/invites (P4.8)
- invites/[token] validate + redeem (P4.8)
- fs_project_dev_servers table + lib/server/dev-server-state.ts (P6.B1)
- fs_project_secrets table + CRUD routes (P6.D2)
- lib/integrations/brief-extract.ts (P3.7)

Documentation:
- app/api/ROUTES.md: full route map with auth + tenant
2026-05-17 19:17:22 -07:00

48 lines
1.7 KiB
TypeScript

/**
* Idle-suspend sweep for Path B dev containers.
*
* POST /api/admin/path-b/idle-sweep[?minutes=30]
* Headers: Authorization: Bearer <NEXTAUTH_SECRET>
*
* Suspends every running dev container whose `last_active_at` is older
* than `minutes` (default 30). Idempotent — re-runs harmlessly.
*
* Wire this to a cron (every 5 min) once the frontend is stable.
* Crontab: "[asterisk]/5 * * * *" running:
* curl -fsS -X POST -H "Authorization: Bearer $SECRET" \
* https://vibnai.com/api/admin/path-b/idle-sweep
*
* Saves money (suspended containers don't bill compute) without
* destroying state — the workspace volume + cache volume persist, and
* the next shell.exec call resumes the service in <5s.
*/
import { NextResponse } from "next/server";
import { suspendIdleContainers } from "@/lib/dev-container";
import { timingSafeStringEq } from "@/lib/server/timing-safe";
export async function POST(request: Request) {
const expected = process.env.NEXTAUTH_SECRET ?? "";
if (!expected) {
return NextResponse.json(
{ error: "NEXTAUTH_SECRET not configured" },
{ status: 503 },
);
}
const auth = request.headers.get("authorization") ?? "";
const bearer = auth.toLowerCase().startsWith("bearer ")
? auth.slice(7).trim()
: "";
if (!bearer || !timingSafeStringEq(expected, bearer)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const url = new URL(request.url);
const minStr = url.searchParams.get("minutes");
const minutes =
minStr && Number.isFinite(Number(minStr))
? Math.max(5, Number(minStr))
: 30;
const result = await suspendIdleContainers(minutes);
return NextResponse.json({ result, idleMinutes: minutes });
}