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 -4
View File
@@ -3,6 +3,7 @@ import { readFile, mkdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { newFixture, type Fixture } from '../helpers/fixture.js';
import { activateFiles } from '../helpers/staged-files.js';
let fixture: Fixture;
@@ -34,7 +35,7 @@ async function seedFleet(): Promise<void> {
}
describe('config baseline + overlay flow', () => {
it('composes baseline + overlay and writes dedicated_sp.cfg on beta open, restores pre-beta state on rollback', async () => {
it('composes baseline + overlay for activation and queues restoration of the pre-beta state', async () => {
await seedFleet();
const { createScript, getLatestVersion } = await import('../../src/lib/server/fleet/scripts.js');
const { openBeta, rollbackBeta } = await import('../../src/lib/server/fleet/beta.js');
@@ -70,9 +71,10 @@ describe('config baseline + overlay flow', () => {
waitForMatch: false
});
// 4. Verify: composed cfg lands at configs/dedicated_sp.cfg (in place,
// NO -beta suffix — the game reads the exact filename).
// The live filename is untouched until activation.
const cfgPath = join(fixture.t5, 't5_zombie_theater_1', 'configs', 'dedicated_sp.cfg');
expect(existsSync(cfgPath)).toBe(false);
await activateFiles('beta', betaId);
expect(existsSync(cfgPath)).toBe(true);
const composed = (await readFile(cfgPath)).toString('utf8');
expect(composed).toContain('set g_inactivity "0"');
@@ -82,7 +84,9 @@ describe('config baseline + overlay flow', () => {
// 5. Rollback restores the pre-beta state. There was no cfg on disk
// beforehand, so rollback should just remove the beta-written file.
await rollbackBeta(betaId);
const rollback = await rollbackBeta(betaId);
expect(await readFile(cfgPath, 'utf8')).toBe(composed);
await activateFiles('campaign', rollback.deploymentId!);
expect(existsSync(cfgPath)).toBe(false);
});
@@ -110,6 +114,7 @@ describe('config baseline + overlay flow', () => {
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
const row = db()
.prepare(`SELECT stage, error FROM beta_servers WHERE beta_id = ? AND server = ?`)
@@ -146,6 +151,8 @@ describe('plugin kind writes to plugin destination', () => {
});
const pluginPath = join(fixture.t5, 't5_zombie_theater_1', 'runtime/plutonium/plugins', 'demo.dll');
expect(existsSync(pluginPath)).toBe(false);
await activateFiles('beta', betaId);
expect(existsSync(pluginPath)).toBe(true);
const bytes = await readFile(pluginPath);
expect(bytes.equals(Buffer.from([0x00, 0x01, 0x02, 0x03]))).toBe(true);