Remove autoscaling action cooldown
This commit is contained in:
@@ -17,7 +17,7 @@ export function scalingAdmission(operationId: string): string | null {
|
||||
const paused = (db().prepare("SELECT value FROM settings WHERE key='autoscaling_paused'").get() as { value: string } | undefined)?.value === '1';
|
||||
if (!row || !liveScaling(row.mode) || !row.owns_map || row.revision !== intent.revision || row.paused_reason || paused) reason = 'Autoscaling policy is paused or changed.';
|
||||
else {
|
||||
const policy: ScalingPolicy = { ...JSON.parse(row.settings), id: row.id, mode: row.mode, revision: row.revision, nextActionAt: 0, pausedReason: null, updatedAt: '' };
|
||||
const policy: ScalingPolicy = { ...JSON.parse(row.settings), id: row.id, mode: row.mode, revision: row.revision, pausedReason: null, updatedAt: '' };
|
||||
const requested = JSON.parse(intent.decision) as ScalingDecision;
|
||||
const snapshot = scalingSnapshot(policy, Date.now(), operationId);
|
||||
if (requested.action === 'create') {
|
||||
|
||||
@@ -15,8 +15,8 @@ import { scalingOwner } from './ownership.js';
|
||||
import { getServer } from '../fleet/servers.js';
|
||||
|
||||
export class ScalingError extends Error { constructor(message: string, public status = 400) { super(message); } }
|
||||
interface PolicyRow { id: string; revision: number; settings: string; mode: ScalingPolicy['mode']; next_action_at: number; paused_reason: string | null; updated_at: string }
|
||||
const present = (row: PolicyRow): ScalingPolicy => ({ ...JSON.parse(row.settings), id: row.id, revision: row.revision, mode: row.mode, nextActionAt: row.next_action_at, pausedReason: row.paused_reason, updatedAt: row.updated_at });
|
||||
interface PolicyRow { id: string; revision: number; settings: string; mode: ScalingPolicy['mode']; paused_reason: string | null; updated_at: string }
|
||||
const present = (row: PolicyRow): ScalingPolicy => ({ ...JSON.parse(row.settings), id: row.id, revision: row.revision, mode: row.mode, pausedReason: row.paused_reason, updatedAt: row.updated_at });
|
||||
export const listScalingPolicies = (): ScalingPolicy[] => (db().prepare('SELECT * FROM autoscaling_policies ORDER BY game,map_slug').all() as PolicyRow[]).map(present);
|
||||
export function getScalingPolicy(id: string): ScalingPolicy | undefined { const row = db().prepare('SELECT * FROM autoscaling_policies WHERE id=?').get(id) as PolicyRow | undefined; return row && present(row); }
|
||||
export const scalingGloballyPaused = (): boolean => (db().prepare("SELECT value FROM settings WHERE key='autoscaling_paused'").get() as { value: string } | undefined)?.value === '1';
|
||||
@@ -27,7 +27,7 @@ export function previewScaling(input: unknown, id?: string, revision?: number):
|
||||
if (existing && existing.revision !== revision) throw new ScalingError('Policy changed. Refresh before editing.', 409);
|
||||
if (existing && (existing.game !== settings.game || existing.mapSlug !== settings.mapSlug)) throw new ScalingError('A policy cannot change its game or map.');
|
||||
const policy: ScalingPolicy = { ...settings, id: existing?.id ?? 'preview', revision: existing?.revision ?? 0,
|
||||
nextActionAt: existing?.nextActionAt ?? 0, pausedReason: null, updatedAt: existing?.updatedAt ?? '' };
|
||||
pausedReason: null, updatedAt: existing?.updatedAt ?? '' };
|
||||
const snapshot = scalingSnapshot(policy);
|
||||
const blockers: string[] = [];
|
||||
if (liveScaling(policy.mode)) {
|
||||
@@ -167,7 +167,6 @@ export async function runAutoscaling(): Promise<boolean> {
|
||||
if (!result.deploymentId) throw new Error('Provisioning did not return an operation identity.');
|
||||
db().prepare('UPDATE autoscaling_decisions SET operation_id=? WHERE id=?').run(result.deploymentId, decisionId);
|
||||
db().prepare("INSERT INTO provisioning_sources(operation_id,kind,source_id,server) VALUES(?,'autoscaling',?,?)").run(result.deploymentId, policy.id, decision.server);
|
||||
db().prepare('UPDATE autoscaling_policies SET next_action_at=? WHERE id=?').run(now + policy.cooldownMinutes * 60_000, policy.id);
|
||||
submitted = true;
|
||||
})();
|
||||
} catch (error) {
|
||||
|
||||
@@ -47,7 +47,6 @@ export function planScaling(policy: ScalingPolicy, snapshot: ScalingSnapshot, no
|
||||
const blocked = members.find(m => !['occupied', 'empty'].includes(m.state));
|
||||
if (blocked) return result('wait', blocked.blocker ?? `Waiting for ${blocked.name}: ${blocked.state}.`);
|
||||
if (members.some(m => m.observedAt === null || m.observedAt > now || now - m.observedAt > ONLINE_MAX_MS)) return result('wait', 'Waiting for fresh occupancy evidence.');
|
||||
if (policy.nextActionAt > now) return result('wait', 'Action cooldown is active.');
|
||||
if (occupied + empty < desired) {
|
||||
if (snapshot.reservedNames.length >= policy.maximum) return result('wait', 'Maximum lobby reservations reached.');
|
||||
if (!snapshot.keyPool.available) return result('wait', snapshot.keyPool.total
|
||||
|
||||
@@ -5,7 +5,7 @@ export function ensureAutoscalingSchema(handle: Database.Database): void {
|
||||
CREATE TABLE IF NOT EXISTS autoscaling_policies (
|
||||
id TEXT PRIMARY KEY, game TEXT NOT NULL, map_slug TEXT NOT NULL,
|
||||
revision INTEGER NOT NULL DEFAULT 1, settings TEXT NOT NULL,
|
||||
mode TEXT NOT NULL DEFAULT 'off', next_action_at INTEGER NOT NULL DEFAULT 0,
|
||||
mode TEXT NOT NULL DEFAULT 'off',
|
||||
paused_reason TEXT, owns_map INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL,
|
||||
UNIQUE(game,map_slug)
|
||||
);
|
||||
@@ -34,9 +34,15 @@ export function ensureAutoscalingSchema(handle: Database.Database): void {
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS autoscaling_sample_retention ON autoscaling_samples(sampled_at);
|
||||
`);
|
||||
// Removed modes must not silently gain permission to destroy lobbies.
|
||||
// Keep ownership while delivered work settles through normal reconciliation.
|
||||
handle.transaction(() => {
|
||||
const columns = handle.pragma('table_info(autoscaling_policies)') as { name: string }[];
|
||||
if (columns.some(column => column.name === 'next_action_at')) {
|
||||
handle.exec('ALTER TABLE autoscaling_policies DROP COLUMN next_action_at');
|
||||
}
|
||||
handle.exec(`UPDATE autoscaling_policies SET settings=json_remove(settings,'$.cooldownMinutes')
|
||||
WHERE json_type(settings,'$.cooldownMinutes') IS NOT NULL`);
|
||||
// Removed modes must not silently gain permission to destroy lobbies.
|
||||
// Keep ownership while delivered work settles through normal reconciliation.
|
||||
handle.prepare(`UPDATE autoscaling_authorizations SET revoked_at=?
|
||||
WHERE claimed_at IS NULL AND revoked_at IS NULL AND policy_id IN
|
||||
(SELECT id FROM autoscaling_policies WHERE mode IN ('dry-run','create-only'))`).run(Date.now());
|
||||
|
||||
Reference in New Issue
Block a user