Require full key coverage when saving autoscaling policies

This commit is contained in:
2026-10-06 23:37:20 -04:00
parent bc744e2840
commit 4dbf704e6f
7 changed files with 104 additions and 7 deletions
+7 -3
View File
@@ -30,12 +30,16 @@ export function previewScaling(input: unknown, id?: string, revision?: number):
nextActionAt: existing?.nextActionAt ?? 0, pausedReason: null, updatedAt: existing?.updatedAt ?? '' };
const snapshot = scalingSnapshot(policy);
const blockers: string[] = [];
// Creating any policy requires full coverage; existing policies must always be pausable.
if (!existing || policy.mode !== 'off') {
for (const map of zombieMaps(policy.game).filter(m => policy.mapSlug === '*' || m.slug === policy.mapSlug)) {
const count = snapshot.keyedNames.filter(name => parseServerName(name)?.mapSlug === map.slug).length;
if (count < policy.maximum) blockers.push(`Save keys for at least ${policy.maximum} lobby identities for ${map.friendly} before saving this policy (${count} saved; ${policy.maximum - count} more required).`);
}
}
if (liveScaling(policy.mode)) {
const owner = scalingOwner(policy.game, policy.mapSlug);
if (owner && owner !== policy.id) blockers.push(`An overlapping policy owns this lineup. Pause it and settle pending work first: /autoscaling#${owner}`);
for (const map of zombieMaps(policy.game).filter(m => policy.mapSlug === '*' || m.slug === policy.mapSlug)) {
if (snapshot.keyedNames.filter(name => parseServerName(name)?.mapSlug === map.slug).length < policy.maximum) blockers.push(`Save keys for at least ${policy.maximum} lobby identities for ${map.friendly} before activating this policy.`);
}
blockers.push(...snapshot.blockers);
for (const member of snapshot.members) if (!['occupied', 'empty'].includes(member.state)) blockers.push(`${member.name}: ${member.blocker ?? member.state}. Settle this work before activation.`);
if (policy.mode === 'create-destroy') for (const member of snapshot.members) {
+2 -1
View File
@@ -1,4 +1,5 @@
import { db } from '../db/index.js';
import { validServerId } from '../fleet/server-keys.js';
import { listServers, type ServerRow } from '../fleet/servers.js';
import { observationReader } from '../fleet/status.js';
import { parseServerName, zombieMaps } from '../../map-aliases.js';
@@ -72,6 +73,6 @@ export function scalingSnapshot(policy: ScalingPolicy, now = Date.now(), exceptO
reservedNames.add(row.name);
if (!members.some(m => m.name === row.name)) blockers.push(`Unresolved native instance ${row.name}.`);
}
const keyedNames = (handle.prepare("SELECT server_id AS name FROM server_keys WHERE length(trim(pluto_key))>0").all() as { name: string }[]).filter(matches).map(r => r.name);
const keyedNames = (handle.prepare("SELECT server_id AS name FROM server_keys WHERE length(trim(pluto_key))>0").all() as { name: string }[]).filter(row => validServerId(row.name) && matches(row)).map(r => r.name);
return { members, reservedNames: [...reservedNames], keyedNames, blockers };
}