import { OOM_FAULT_ERROR, type OOMFaultObservationIR, type OOMFaultPoint } from "../../../protocol/oom-recovery"; export interface OOMFaultConfiguration { point: OOMFaultPoint; failAfterBytes?: number; failAfterCount?: number; } export interface OOMFaultSessionStats { active: boolean; currentBytes: number; peakBytes: number; liveResources: number; peakResources: number; releasedBytes: number; allocationCount: number; matchingAllocationCount: number; unauthorizedAttempts: number; triggered: boolean; } export interface OOMAllocationLease { readonly id: string; readonly bytes: number; readonly tracked: boolean; release(): void; } export class OOMFaultInjectedError extends Error { readonly name = "OOMFaultInjectedError"; readonly observation: OOMFaultObservationIR; constructor(observation: OOMFaultObservationIR) { super(`${observation.code}: deterministic allocation failure at ${observation.stage}`); this.observation = observation; } } export class OOMFaultSessionAccessError extends Error { readonly name = "OOMFaultSessionAccessError"; } function threshold(value: number | undefined, label: string): number | undefined { if (value === undefined) return undefined; if (!Number.isSafeInteger(value) || value < 0) throw new Error(`OOM_FAULT_CONFIG_INVALID: ${label}`); return value; } export class OOMFaultSession { private readonly token: string; private readonly configuration: OOMFaultConfiguration; private readonly resources = new Map(); private active = true; private currentBytes = 0; private peakBytes = 0; private peakResources = 0; private releasedBytes = 0; private allocationCount = 0; private matchingAllocationCount = 0; private matchingBytes = 0; private unauthorizedAttempts = 0; private triggered = false; constructor(configuration: OOMFaultConfiguration, token: string) { if (!OOM_FAULT_ERROR[configuration.point]) throw new Error("OOM_FAULT_CONFIG_INVALID: point"); const failAfterBytes = threshold(configuration.failAfterBytes, "failAfterBytes"); const failAfterCount = threshold(configuration.failAfterCount, "failAfterCount"); if (failAfterBytes === undefined && failAfterCount === undefined) throw new Error("OOM_FAULT_CONFIG_INVALID: threshold required"); if (!/^[a-f0-9-]{36,128}$/.test(token)) throw new Error("OOM_FAULT_CONFIG_INVALID: token"); this.configuration = { ...configuration, failAfterBytes, failAfterCount }; this.token = token; } reserve(token: string, point: OOMFaultPoint, bytes: number, resourceId: string = crypto.randomUUID()): OOMAllocationLease { if (!this.active) return { id: resourceId, bytes, tracked: false, release: () => undefined }; if (token !== this.token) { this.unauthorizedAttempts++; throw new OOMFaultSessionAccessError("OOM_FAULT_TOKEN_MISMATCH: fault session token is isolated"); } if (!Number.isSafeInteger(bytes) || bytes < 0 || !resourceId || this.resources.has(resourceId)) throw new Error("OOM_FAULT_ALLOCATION_INVALID"); this.allocationCount++; if (point === this.configuration.point) { const nextCount = this.matchingAllocationCount + 1; const nextBytes = this.matchingBytes + bytes; const countExceeded = this.configuration.failAfterCount !== undefined && this.matchingAllocationCount >= this.configuration.failAfterCount; const bytesExceeded = this.configuration.failAfterBytes !== undefined && nextBytes > this.configuration.failAfterBytes; this.matchingAllocationCount = nextCount; this.matchingBytes = nextBytes; if (countExceeded || bytesExceeded) { this.triggered = true; const mapping = OOM_FAULT_ERROR[point]; throw new OOMFaultInjectedError({ point, ...mapping, attemptedBytes: bytes, allocationCount: nextCount, failAfterBytes: this.configuration.failAfterBytes, failAfterCount: this.configuration.failAfterCount, }); } } this.resources.set(resourceId, bytes); this.currentBytes += bytes; this.peakBytes = Math.max(this.peakBytes, this.currentBytes); this.peakResources = Math.max(this.peakResources, this.resources.size); let released = false; return { id: resourceId, bytes, tracked: true, release: () => { if (released) return; released = true; const allocated = this.resources.get(resourceId); if (allocated === undefined) return; this.resources.delete(resourceId); this.currentBytes -= allocated; this.releasedBytes += allocated; }, }; } close(token: string): OOMFaultSessionStats { if (this.active && token !== this.token) { this.unauthorizedAttempts++; throw new OOMFaultSessionAccessError("OOM_FAULT_TOKEN_MISMATCH: fault session token is isolated"); } this.active = false; return this.stats(); } stats(): OOMFaultSessionStats { return { active: this.active, currentBytes: this.currentBytes, peakBytes: this.peakBytes, liveResources: this.resources.size, peakResources: this.peakResources, releasedBytes: this.releasedBytes, allocationCount: this.allocationCount, matchingAllocationCount: this.matchingAllocationCount, unauthorizedAttempts: this.unauthorizedAttempts, triggered: this.triggered, }; } } export function beginOOMFaultSession(configuration: OOMFaultConfiguration): { session: OOMFaultSession; token: string } { const token = `${crypto.randomUUID()}-${crypto.randomUUID()}`; return { session: new OOMFaultSession(configuration, token), token }; } export function assertFault(error: unknown, point: OOMFaultPoint): OOMFaultObservationIR { if (!(error instanceof OOMFaultInjectedError) || error.observation.point !== point) throw error; return error.observation; }