Add configurable beta test plans and staged rollout

This commit is contained in:
2026-09-17 21:35:28 -04:00
parent 5b5266da3a
commit a50eae9d21
26 changed files with 1518 additions and 44 deletions
+23 -2
View File
@@ -2,6 +2,19 @@ import { json } from '@sveltejs/kit';
import { getServer, listServers } from '$lib/server/fleet/servers.js';
import { saveTelemetry } from '$lib/server/fleet/telemetry.js';
import { emit } from '$lib/server/pipeline/bus.js';
import { db } from '$lib/server/db/index.js';
import { acceptTestSignal, parseTestSignal } from '$lib/server/fleet/beta-tests.js';
// Reporters fetch this once at process startup and keep the token for that boot.
export async function GET({ url }: { url: URL }) {
const server = url.searchParams.get('server');
if (!server || !getServer(server)) return json({ error: 'Unknown server' }, { status: 404 });
const runs = db().prepare(`SELECT r.id AS runId, r.beta_id AS betaId FROM beta_test_runs r
JOIN betas b ON b.id = r.beta_id JOIN beta_test_plans p ON p.beta_id = b.id
WHERE r.server = ? AND r.retired_at IS NULL AND b.state IN ('running','ready-promote')
AND p.outcome IN ('active','passed')`).all(server);
return json({ runs }, { headers: { 'Cache-Control': 'no-store' } });
}
export async function POST({ request }: { request: Request }) {
console.log('[telemetry] received POST /api/telemetry');
@@ -30,7 +43,7 @@ export async function POST({ request }: { request: Request }) {
return reject(400, 'Payload must be a JSON object');
}
const issues: string[] = [];
if (body.event !== 'round_change') issues.push('event must equal "round_change"');
if (body.runId === undefined && body.event !== 'round_change') issues.push('Session telemetry requires runId');
if (typeof body.server !== 'string' || !body.server.length || body.server.length > 255) {
issues.push('server must be a registered server name (1–255 characters)');
}
@@ -56,6 +69,14 @@ export async function POST({ request }: { request: Request }) {
receivedServer: body.server, destroyedAt: server.destroyed_at
});
}
let test: { accepted: boolean; reason?: string } | undefined;
if (body.runId !== undefined) {
try {
const signal = parseTestSignal(body);
test = acceptTestSignal(body.server, body.runId, body.eventId, signal);
} catch (error) { return reject(400, (error as Error).message); }
if (body.event !== 'round_change') return json({ ok: true, test });
}
const telemetry = {
server: body.server as string, round: body.round as number,
players: body.players as number, receivedAt: new Date().toISOString()
@@ -63,5 +84,5 @@ export async function POST({ request }: { request: Request }) {
saveTelemetry(telemetry);
console.log(`[telemetry] accepted round_change ${JSON.stringify(telemetry)}`);
emit({ type: 'server-telemetry', entries: [telemetry] });
return json({ ok: true, telemetry });
return json({ ok: true, telemetry, test });
}