import type Database from 'better-sqlite3'; export function ensureAutoscalingSchema(handle: Database.Database): void { handle.exec(` 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, paused_reason TEXT, owns_map INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL, UNIQUE(game,map_slug) ); CREATE TABLE IF NOT EXISTS autoscaling_members ( policy_id TEXT NOT NULL REFERENCES autoscaling_policies(id), server TEXT NOT NULL, identity TEXT NOT NULL, observation TEXT NOT NULL, adopted INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(policy_id,server) ); CREATE TABLE IF NOT EXISTS autoscaling_decisions ( id TEXT PRIMARY KEY, policy_id TEXT NOT NULL REFERENCES autoscaling_policies(id), revision INTEGER NOT NULL, evaluated_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, fingerprint TEXT NOT NULL, decision TEXT NOT NULL, evidence TEXT NOT NULL, operation_id TEXT UNIQUE REFERENCES server_deployments(id), resolved INTEGER NOT NULL DEFAULT 1 ); CREATE UNIQUE INDEX IF NOT EXISTS autoscaling_one_intent ON autoscaling_decisions(policy_id) WHERE resolved=0; CREATE INDEX IF NOT EXISTS autoscaling_history ON autoscaling_decisions(policy_id,evaluated_at DESC); CREATE INDEX IF NOT EXISTS autoscaling_decision_retention ON autoscaling_decisions(last_seen_at) WHERE resolved=1; CREATE TABLE IF NOT EXISTS autoscaling_samples ( policy_id TEXT NOT NULL REFERENCES autoscaling_policies(id), sampled_at INTEGER NOT NULL, mode TEXT NOT NULL, occupied INTEGER NOT NULL, empty INTEGER NOT NULL, unknown INTEGER NOT NULL, deficit INTEGER NOT NULL, PRIMARY KEY(policy_id,sampled_at) ); CREATE TABLE IF NOT EXISTS autoscaling_authorizations ( operation_id TEXT PRIMARY KEY, policy_id TEXT NOT NULL, revision INTEGER NOT NULL, expires_at INTEGER NOT NULL, claimed_at INTEGER, revoked_at INTEGER ); 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(() => { 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()); handle.prepare(`UPDATE autoscaling_policies SET mode='off', settings=json_set(settings,'$.mode','off'), revision=revision+1, paused_reason='This autoscaling mode was removed. Review and enable Create and destroy to resume.', owns_map=CASE WHEN EXISTS (SELECT 1 FROM autoscaling_decisions WHERE policy_id=autoscaling_policies.id AND resolved=0) THEN owns_map ELSE 0 END, updated_at=? WHERE mode IN ('dry-run','create-only')`).run(new Date().toISOString()); })(); }