Defer server file changes until guarded restart activation

Persist beta, promotion, drift, and rollback payloads until occupancy and restart-capacity checks pass. Preserve rollback snapshots and campaign ordering, and allow held rollback campaigns to resume safely.

Fixes #28

Validation: 805 tests passed, Svelte checks clean, production build passed.
This commit is contained in:
2026-09-27 23:06:16 -04:00
parent eb2a852fdc
commit fb48f64de1
36 changed files with 1634 additions and 421 deletions
+11 -1
View File
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
import { activateFiles } from '../helpers/staged-files.js';
let fixture: Fixture;
@@ -62,9 +63,12 @@ describe('editing promoted script scopes', () => {
scripts: [{ scriptId: script.id, scriptVersionId: version.id }],
servers: ['t5_zombie_theater_1'], waitForHealth: false, waitForMatch: false
});
await activateFiles('beta', betaId);
db().prepare("UPDATE beta_servers SET stage = 'ready' WHERE beta_id = ?").run(betaId);
db().prepare("UPDATE betas SET state = 'ready-promote' WHERE id = ?").run(betaId);
return promoteBeta(betaId);
const result = await promoteBeta(betaId);
await activateFiles('campaign', result.deploymentId);
return result;
}
await promote();
@@ -105,6 +109,7 @@ describe('editing promoted script scopes', () => {
scripts: [{ scriptId: script.id, scriptVersionId: version.id }],
servers: ['t5_zombie_theater_1'], waitForHealth: false, waitForMatch: false
});
await activateFiles('beta', betaId);
db().prepare('UPDATE betas SET state = ? WHERE id = ?').run(state, betaId);
const inventory = db().prepare('SELECT * FROM server_scripts').all();
expect(inventory.length).toBeGreaterThan(0);
@@ -143,6 +148,7 @@ describe('promote creates a deployment', () => {
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
// Force all beta_servers to 'ready' and beta to 'ready-promote'.
db().prepare(`UPDATE beta_servers SET stage = 'ready', restarted_at = ? WHERE beta_id = ?`)
@@ -216,6 +222,7 @@ describe('rollback beta on ready-promote marks version test-failed', () => {
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
// Force ready-promote so the state machine gate is closed.
db().prepare(`UPDATE beta_servers SET stage = 'ready' WHERE beta_id = ?`).run(betaId);
@@ -253,10 +260,12 @@ describe('rollback promote deployment', () => {
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
db().prepare(`UPDATE beta_servers SET stage = 'ready', restarted_at = ? WHERE beta_id = ?`)
.run(new Date().toISOString(), betaId);
db().prepare(`UPDATE betas SET state = 'ready-promote' WHERE id = ?`).run(betaId);
const promote = await promoteBeta(betaId);
await activateFiles('campaign', promote.deploymentId);
// Sanity: script is promoted.
expect(getScript(script.id)!.current_version_id).toBe(v.id);
@@ -329,6 +338,7 @@ describe('multi-script beta', () => {
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
// Both scripts are joined to the beta.
const bundle = listBetaScripts(betaId);