feat: harden orchestrator execution runtime
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user