Promote as a real deployment; version state and scope dropdowns

Turns promotion into a first-class deployment (action='promote') so it lands
in the deployments UI with before/after content, per-server restart tracking,
and one-click rollback. Non-beta matching-scope servers get restart_state=
pending so the existing sweep drains and restarts them the same way drift does.

Also adds a per-version state (untested/tested/test-failed) so the upload
flow can visibly signal that a beta must land before promotion, and swaps
the scope-value text box for game/map dropdowns populated from the fleet.
Beta rollback at ready-promote is now labelled Reject and marks the version
test-failed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 14:24:35 -04:00
co-authored by Claude Opus 4.7
parent 3f934fd579
commit 1afdfddc87
16 changed files with 1014 additions and 41 deletions
+15 -1
View File
@@ -66,9 +66,23 @@ function reconcileBetaState(betaId: number): void {
.all(betaId) as Array<{ stage: BetaStage }>;
if (rows.length === 0) return;
if (rows.every((r) => r.stage === 'ready')) {
db()
const upd = db()
.prepare(`UPDATE betas SET state = 'ready-promote' WHERE id = ? AND state = 'running'`)
.run(betaId);
if (upd.changes > 0) {
// The version that just cleared its beta gate is now considered
// tested. This survives promote (tested → promoted is derived from
// scripts.current_version_id, not stored) and reject (rollbackBeta
// demotes back to test-failed).
const beta = db()
.prepare(`SELECT script_version_id FROM betas WHERE id = ?`)
.get(betaId) as { script_version_id: number } | undefined;
if (beta) {
db()
.prepare(`UPDATE script_versions SET state = 'tested' WHERE id = ?`)
.run(beta.script_version_id);
}
}
emit({ type: 'beta-state', betaId, state: 'ready-promote' });
}
}