import { db } from '../db/index.js'; import type { RebalanceCandidate, RebalanceSettings } from '../../rebalancing.js'; import { rebalanceMoveBlocker } from './planner.js'; import { rebalanceSnapshot } from './snapshot.js'; /** A delivered migration must finish even after the operator stops its parent run. */ export function rebalanceAdmission(operationId: string): string | null { const operation = db() .prepare('SELECT triggered_by FROM server_deployments WHERE id=?') .get(operationId) as { triggered_by: string } | undefined; if (!operation?.triggered_by.startsWith('rebalance:')) return null; const migration = db() .prepare( 'SELECT remove_operation_id,removed_at FROM ec_host_migrations WHERE deployment_id=?' ) .get(operationId) as { remove_operation_id: string | null; removed_at: string | null } | undefined; if (migration?.remove_operation_id || migration?.removed_at) return null; const row = db() .prepare( `SELECT m.candidate,r.settings,r.state FROM rebalancing_moves m JOIN rebalancing_runs r ON r.id=m.run_id WHERE m.operation_id=? AND m.state='moving'` ) .get(operationId) as { candidate: string; settings: string; state: string } | undefined; if (!row || row.state !== 'running') return 'Rebalance run is no longer requesting this move.'; const settings = JSON.parse(row.settings) as RebalanceSettings; return rebalanceMoveBlocker( rebalanceSnapshot(settings, operationId), JSON.parse(row.candidate) as RebalanceCandidate ); } /** Cancelling an individual uncommitted move also withdraws the rest of that run. */ export function requestRebalanceStopForOperation(operationId: string): void { db() .prepare( `UPDATE rebalancing_runs SET state='stopping',updated_at=?,message='Stopping after the current move.' WHERE state='running' AND id IN (SELECT run_id FROM rebalancing_moves WHERE operation_id=?)` ) .run(new Date().toISOString(), operationId); }