Files
patchpass/Backend/src/lib/Limits.ts
T
Space-Banane 7e05dd918c
Deploy / Build (push) Successful in 28s
Deploy / Test & Lint (push) Failing after 29s
Deploy / Build and Push Docker Image (push) Has been skipped
Patchpass V1
2026-07-18 20:14:44 +02:00

91 lines
3.5 KiB
TypeScript

import { prisma } from "./db";
// ── Platform limits ────────────────────────────────────────────────────────────
/** Max change requests an agent may create per rolling hour. */
export const MAX_REQUESTS_PER_HOUR = 15;
/** Max agents a single human account may own. */
export const MAX_AGENTS_PER_USER = 5;
/** Pending-request cap bounds (human-configurable per agent). */
export const MIN_PENDING_LIMIT = 1;
export const MAX_PENDING_LIMIT = 10;
export const DEFAULT_PENDING_LIMIT = 5;
/** Request expiry bounds, in seconds. */
export const DEFAULT_EXPIRY_SECONDS = 30 * 60; // 30 minutes
export const MIN_EXPIRY_SECONDS = 60; // 1 minute
export const MAX_EXPIRY_SECONDS = 12 * 60 * 60; // 12 hours
/** Auto-delete retention bounds, in days. */
export const MIN_AUTO_DELETE_DAYS = 7;
export const DEFAULT_AUTO_DELETE_DAYS = 30;
// ── Pure clamps ────────────────────────────────────────────────────────────────
export function clampPendingLimit(value: number): number {
if (!Number.isFinite(value)) return DEFAULT_PENDING_LIMIT;
return Math.min(MAX_PENDING_LIMIT, Math.max(MIN_PENDING_LIMIT, Math.trunc(value)));
}
export function clampExpirySeconds(value: number | undefined | null): number {
if (value === undefined || value === null || !Number.isFinite(value)) {
return DEFAULT_EXPIRY_SECONDS;
}
return Math.min(MAX_EXPIRY_SECONDS, Math.max(MIN_EXPIRY_SECONDS, Math.trunc(value)));
}
export function clampAutoDeleteDays(value: number): number {
if (!Number.isFinite(value)) return DEFAULT_AUTO_DELETE_DAYS;
return Math.max(MIN_AUTO_DELETE_DAYS, Math.trunc(value));
}
// ── Limit checks (DB-backed) ────────────────────────────────────────────────────
export type LimitResult = { allowed: true } | { allowed: false; reason: string };
/** An agent may create at most MAX_REQUESTS_PER_HOUR requests per rolling hour. */
export async function checkAgentHourlyLimit(agentId: number): Promise<LimitResult> {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const count = await prisma.changeRequest.count({
where: { agentId, createdAt: { gte: oneHourAgo } },
});
if (count >= MAX_REQUESTS_PER_HOUR) {
return {
allowed: false,
reason: `Rate limit reached: an agent may create at most ${MAX_REQUESTS_PER_HOUR} requests per hour`,
};
}
return { allowed: true };
}
/** An agent may have at most `maxPendingRequests` requests in PENDING state. */
export async function checkAgentPendingLimit(
agentId: number,
maxPendingRequests: number,
): Promise<LimitResult> {
const count = await prisma.changeRequest.count({
where: { agentId, state: "PENDING" },
});
if (count >= maxPendingRequests) {
return {
allowed: false,
reason: `Pending limit reached: this agent may have at most ${maxPendingRequests} pending requests at a time`,
};
}
return { allowed: true };
}
/** A human may own at most MAX_AGENTS_PER_USER agents. */
export async function checkAgentCountLimit(userId: number): Promise<LimitResult> {
const count = await prisma.agent.count({ where: { ownerId: userId } });
if (count >= MAX_AGENTS_PER_USER) {
return {
allowed: false,
reason: `Agent limit reached: a human may own at most ${MAX_AGENTS_PER_USER} agents`,
};
}
return { allowed: true };
}