56 lines
3.7 KiB
TypeScript
56 lines
3.7 KiB
TypeScript
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',
|
|
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);
|
|
`);
|
|
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());
|
|
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());
|
|
})();
|
|
}
|