Files
electric-cherry/src/lib/server/autoscaling/schema.ts
T

50 lines
3.3 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', 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());
})();
}