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
+10 -2
View File
@@ -84,9 +84,11 @@ describe('durable beta test plans', () => {
await tick(); await tick();
expect(existsSync(scriptPath(names[1]))).toBe(false);
for (let i = 0; i < 4; i++) await tick();
expect(existsSync(scriptPath(names[1]))).toBe(true);
expect(existsSync(scriptPath(names[1]))).toBe(false);
expect(getBeta(id)?.state).toBe('running');
expect(listBetaServers(id)[1].stage).toBe('awaiting-empty');
await tick();
expect(existsSync(scriptPath(names[1]))).toBe(true);
for (let i = 0; i < 6; i++) await tick();
expect(getBeta(id)?.state).toBe('ready-promote');
});
@@ -120,6 +122,9 @@ describe('durable beta test plans', () => {
expect(testView(id)?.servers[0].progress[0].current).toBe(25);
expect(testView(id)?.evidence.filter(e => e.kind === 'round_change')).toHaveLength(1);
await tick();
expect(existsSync(scriptPath(names[1]))).toBe(false);
await postTelemetry(gameEvent('heartbeat', 99, 0, names[1]));
await tick();
expect(existsSync(scriptPath(names[1]))).toBe(true);
});
@@ -170,9 +175,12 @@ describe('durable beta test plans', () => {
const { getBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
expect(getBeta(id)?.state).toBe('rolled-back');
expect(await readFile(scriptPath(names[0]), 'utf8')).toBe('original');
expect(await readFile(scriptPath(names[0]), 'utf8')).toBe('main() {}');
expect(existsSync(scriptPath(names[1]))).toBe(false);
expect(db().prepare("SELECT server, restart_state FROM deployment_servers").all()).toEqual([{ server: names[0], restart_state: 'pending' }]);
const { sweep } = await import('../helpers/provisioning.js');
await sweep();
expect(await readFile(scriptPath(names[0]), 'utf8')).toBe('original');
});
it('keeps snapshots and holds after a paused failure, without deploying subsequent waves', async () => {