import { z } from "zod"; import { AnalysisReviewError, canonicalJson, isDiagnosticPlainRecord, MAX_DIAGNOSTIC_JSON_DEPTH, MAX_DIAGNOSTIC_JSON_NODES, type AnalysisReviewFinding } from "@actuarial-ts/core"; import { parseReviewBoundary } from "./reviewBoundary.js"; import type { DiagnosticRunInput, DiagnosticExecutionGateReceipt } from "./diagnosticInput.js"; /** Bounds for the finite-JSON execution gateway, not latency or peak-memory estimates. */ export const DIAGNOSTIC_EXECUTION_LIMITS = Object.freeze({ maximumInputRows: 100000, maximumPreparationPositions: 1000000, maximumPreparedCells: 100000, maximumMetricPositions: 1000000, maximumReviewEvaluationPositions: 1000000, maximumInputCharacters: 20000000 }); export interface DiagnosticExecutionRequest { readonly source: DiagnosticRunInput; readonly limits: { readonly [K in keyof typeof DIAGNOSTIC_EXECUTION_LIMITS]: number }; } export interface DiagnosticExecutionPreflight { readonly storage: "eager" | "compact"; readonly work: { readonly inputRows: number; readonly preparationPositionUpperBound: number; readonly preparedCells: number; readonly metricPositionUpperBound: number; readonly reviewEvaluationUpperBound: number }; readonly policy: Pick; readonly findings: readonly AnalysisReviewFinding[]; readonly assurance: "prepared-input-only"; } export const diagnosticExecutionRequestSchema: z.ZodType = z.object({ // Preserve the complete native payload. The existing validator compiles its // definition, owns its rows and validates grouping and execution policy. source: z.custom(isDiagnosticPlainRecord), limits: z.object({ maximumInputRows: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumInputRows), maximumPreparationPositions: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumPreparationPositions), maximumPreparedCells: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumPreparedCells), maximumMetricPositions: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumMetricPositions), maximumReviewEvaluationPositions: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumReviewEvaluationPositions), maximumInputCharacters: z.number().int().positive().max(DIAGNOSTIC_EXECUTION_LIMITS.maximumInputCharacters), }).strict(), }).strict(); /** Internal shared refusal; its path always refers to the complete execution request. */ export function diagnosticExecutionLimit(path: string, message: string): never { throw new AnalysisReviewError([{ code: "RESOURCE_LIMIT_EXCEEDED", path, message, remedies: ["Supply sufficient limits within the published ceilings or explicitly reduce the diagnostic source/selection"] }]); } /** Internal: own a request already checked by the safe-JSON boundary, preserving native free-metadata semantics. */ export function ownDiagnosticGatewayRequest(value: T): T { const own = (item: unknown): unknown => { if (item === null || typeof item !== "object") return item; if (Array.isArray(item)) return Object.freeze(item.map(own)); const copy = Object.create(Object.getPrototypeOf(item)); for (const key of Object.keys(item)) Object.defineProperty(copy, key, { value: own((item as Record)[key]), enumerable: true }); return Object.freeze(copy); }; return own(value) as T; } /** Safe JSON plus declared raw-input bounds; native semantic validation is separate. */ export function parseDiagnosticExecutionRequest(input: unknown): DiagnosticExecutionRequest { const request = parseReviewBoundary(diagnosticExecutionRequestSchema, input); if (canonicalJson(request).length > request.limits.maximumInputCharacters) diagnosticExecutionLimit("$.limits.maximumInputCharacters", "Diagnostic execution exceeds its input character budget"); const inputRows = [request.source.losses, request.source.exposures, request.source.expectedCells].reduce((sum, rows) => sum + (Array.isArray(rows) ? rows.length : 0), 0); if (inputRows > request.limits.maximumInputRows) diagnosticExecutionLimit("$.limits.maximumInputRows", "Diagnostic execution exceeds its raw loss/exposure/expected-cell row budget"); return ownDiagnosticGatewayRequest(request); } /** Descriptor limits include the shared safe-JSON scanner's independent ceilings. */ export const DIAGNOSTIC_EXECUTION_BOUNDARY_LIMITS = Object.freeze({ ...DIAGNOSTIC_EXECUTION_LIMITS, maximumJsonDepth: MAX_DIAGNOSTIC_JSON_DEPTH, maximumJsonNodes: MAX_DIAGNOSTIC_JSON_NODES });