/** * Deterministic PRNG + sampling primitives backing the CEL `randomSample` * function. * * STABILITY CONTRACT — DO NOT CHANGE THESE ALGORITHMS. * The same seed must produce the same sample on every runtime and every SDK * version: the backend (server) and frontend (client) rely on agreeing outputs, * and customers rely on seeds for reproducible workflow runs. Golden-value * tests in tests/lib/cel/prng.test.ts pin the exact outputs. */ /** A source of random floats in [0, 1). */ export type RandomSource = () => number; /** * mulberry32: fast 32-bit PRNG with exact, platform-independent output. * `seed` is coerced to uint32 (via `>>> 0`); use {@link normalizeSeed} to * convert arbitrary CEL ints before calling this. */ export function mulberry32(seed: number): RandomSource { let a = seed >>> 0; return () => { a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } /** Normalize a CEL int (bigint or number) to a uint32 PRNG seed. */ export function normalizeSeed(seed: bigint | number): number { return Number(BigInt.asUintN(32, BigInt(seed))); } /** * Sample `count` distinct elements via partial Fisher–Yates. * `count` is clamped to [0, list.length]; the input list is not mutated. */ export function sampleWithoutReplacement(list: readonly T[], count: number, rand: RandomSource): T[] { const n = list.length; const k = Math.min(Math.max(count, 0), n); if (k === 0) return []; const pool = list.slice(); for (let i = 0; i < k; i++) { const j = i + Math.floor(rand() * (n - i)); const tmp = pool[i]!; pool[i] = pool[j]!; pool[j] = tmp; } return pool.slice(0, k); } /** `count` independent draws; duplicates allowed. Returns [] if the list is empty or count <= 0. */ export function sampleWithReplacement(list: readonly T[], count: number, rand: RandomSource): T[] { const n = list.length; if (n === 0 || count <= 0) return []; const out: T[] = []; for (let i = 0; i < count; i++) { out.push(list[Math.floor(rand() * n)]!); } return out; }