Files
pbakaus_impeccable/functions/api/roll.js
T
Paul BakausandClaude Fable 5 b5ec969c07 Add world roll API and seed telemetry client
/api/roll deals deterministic challenger rolls server-side (same salts and
sha256 ranking as the local seed, verified bit-for-bit); the request log is
the impression record. /api/chosen takes the anonymous choice ping. Events
land in Workers Analytics Engine.

concept-seed.mjs resolves data in order: local catalog dir, roll API,
degraded promotion-only seed. --chosen sends the choice ping; DO_NOT_TRACK
and IMPECCABLE_NO_TELEMETRY disable it. API-dealt seeds carry the telemetry
instruction inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:17:09 -07:00

49 lines
2.1 KiB
JavaScript

// GET /api/roll?scope=direction|surface&mode=<persuade|operate|read|experience>&key=<key>&reroll=<n>
//
// Deals a deterministic concept roll: six challengers (two per translation
// tier, rating-weighted) plus one mode-matched staging. Same key + same pool
// revision reproduces the roll. The request itself is the impression record.
import { rollSeed, logEvent, SEED_MODES, CORS_HEADERS } from './_worldroll.js';
export async function onRequestOptions() {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
export async function onRequestGet({ request, env }) {
const url = new URL(request.url);
const scope = url.searchParams.get('scope') || 'surface';
const mode = url.searchParams.get('mode') || null;
const key = url.searchParams.get('key') || crypto.randomUUID().slice(0, 8);
const reroll = Number(url.searchParams.get('reroll') || 0);
if (scope !== 'direction' && scope !== 'surface') {
return Response.json({ error: 'scope must be direction or surface' }, { status: 400, headers: CORS_HEADERS });
}
if (mode !== null && !SEED_MODES.has(mode)) {
return Response.json({ error: 'mode must be persuade, operate, read, or experience' }, { status: 400, headers: CORS_HEADERS });
}
if (!Number.isInteger(reroll) || reroll < 0 || reroll > 8) {
return Response.json({ error: 'reroll must be an integer between 0 and 8' }, { status: 400, headers: CORS_HEADERS });
}
if (!/^[a-z0-9-]{1,64}$/i.test(key)) {
return Response.json({ error: 'key must be 1-64 alphanumeric characters' }, { status: 400, headers: CORS_HEADERS });
}
try {
const roll = await rollSeed({ scope, key, mode, reroll });
logEvent(env, 'roll', {
scope,
mode,
reroll,
poolRevision: roll.poolRevision,
dealtIds: [...roll.challengers.map(challenger => challenger.id), roll.staging?.id].filter(Boolean),
});
return Response.json(roll, {
headers: { ...CORS_HEADERS, 'Cache-Control': 'no-store' },
});
} catch (error) {
return Response.json({ error: error.message }, { status: 500, headers: CORS_HEADERS });
}
}