Files
memind/services/orchestrator/executor-admission-policy.mjs
T

103 lines
3.4 KiB
JavaScript

function clampInteger(value, fallback, min, max) {
const number = Number(value);
if (!Number.isFinite(number)) return fallback;
return Math.min(max, Math.max(min, Math.floor(number)));
}
function normalizeList(value) {
const items = Array.isArray(value)
? value
: String(value ?? '').split(',');
return [...new Set(items.map((item) => String(item).trim()).filter(Boolean))];
}
export function normalizeExecutorAdmissionConfig(input = {}) {
return {
tenantAllowlist: normalizeList(input.tenantAllowlist),
userAllowlist: normalizeList(input.userAllowlist),
workspaceKindAllowlist: normalizeList(
input.workspaceKindAllowlist ?? ['workspace-alias'],
),
maxGlobalActive: clampInteger(input.maxGlobalActive, 20, 1, 10_000),
maxTenantActive: clampInteger(input.maxTenantActive, 5, 1, 1_000),
maxUserActive: clampInteger(input.maxUserActive, 2, 1, 100),
maxTenantPerMinute: clampInteger(input.maxTenantPerMinute, 30, 1, 100_000),
maxUserPerMinute: clampInteger(input.maxUserPerMinute, 10, 1, 10_000),
};
}
export function createExecutorAdmissionPolicy({
store,
config = {},
nowMs = Date.now,
} = {}) {
if (typeof store?.getAdmissionStats !== 'function') {
throw new Error('Executor admission policy requires store.getAdmissionStats');
}
const normalized = normalizeExecutorAdmissionConfig(config);
function denied(reason, details = {}) {
return {
version: 'executor-admission-v1',
allowed: false,
reason,
details,
};
}
return {
config: normalized,
async evaluate(request) {
const tenantId = request.subject?.tenantId;
const userId = request.subject?.userId;
const workspaceKind = request.task?.workspaceRef?.kind;
if (normalized.tenantAllowlist.length && !normalized.tenantAllowlist.includes(tenantId)) {
return denied('executor_tenant_not_allowed');
}
if (normalized.userAllowlist.length && !normalized.userAllowlist.includes(userId)) {
return denied('executor_user_not_allowed');
}
if (!normalized.workspaceKindAllowlist.includes(workspaceKind)) {
return denied('executor_workspace_kind_not_allowed', { workspaceKind });
}
const stats = await store.getAdmissionStats({
tenantId,
userId,
now: nowMs(),
windowMs: 60_000,
});
const checks = [
['globalActive', normalized.maxGlobalActive, 'executor_global_concurrency_limited'],
['tenantActive', normalized.maxTenantActive, 'executor_tenant_concurrency_limited'],
['userActive', normalized.maxUserActive, 'executor_user_concurrency_limited'],
['tenantRecent', normalized.maxTenantPerMinute, 'executor_tenant_rate_limited'],
['userRecent', normalized.maxUserPerMinute, 'executor_user_rate_limited'],
];
for (const [field, limit, reason] of checks) {
if (stats[field] >= limit) return denied(reason, { current: stats[field], limit });
}
return {
version: 'executor-admission-v1',
allowed: true,
reason: 'executor_admitted',
details: {
globalActive: stats.globalActive,
tenantActive: stats.tenantActive,
userActive: stats.userActive,
},
};
},
status() {
return {
version: 'executor-admission-policy-v1',
...normalized,
};
},
};
}
export const executorAdmissionPolicyInternals = {
clampInteger,
normalizeList,
};