import { z } from "zod"; import { isDiagnosticToken, isRealIsoDate, RECIPE_EXPOSURE_LIMITS, projectRecipeExposure, attachRecipeExposure, type RecipeExposureProjectionRequest, type RecipeExposureProjection, type AttachedRecipeExposure, type AttachRecipeExposureRequest } from "@actuarial-ts/core"; import { analysisSnapshotSchema, analysisContextSchema, quantitySemanticsSchema } from "./reviewContracts.js"; import { reviewRecipePreparationSchema } from "./recipeDevelopmentContracts.js"; import { parseReviewBoundary } from "./reviewBoundary.js"; const token = z.string().refine(isDiagnosticToken, "Expected a valid identifier"); const date = z.string().refine(isRealIsoDate, "Expected a real ISO date"); export const exposurePopulationRelationshipSchema = z.object({ actorId: token, knownDate: date, rationale: token }).strict(); /** Internal builder shared by source inputs and typed graph links. */ export function recipeExposureSchemaForPreparation(preparation: T) { return z.object({ id: token, version: token, preparation, measureId: token, origins: z.array(z.object({ id: token, startDate: date, endDate: date }).strict()).min(1).max(RECIPE_EXPOSURE_LIMITS.maximumOrigins), coverage: z.enum(["exact-universe", "declared-subset"]), knownDate: date, meaning: z.object({ quantity: quantitySemanticsSchema, basis: analysisSnapshotSchema, actorId: token, knownDate: date, rationale: token }).strict(), limits: z.object({ maximumOrigins: z.number().int().positive().max(RECIPE_EXPOSURE_LIMITS.maximumOrigins), maximumObservations: z.number().int().positive().max(RECIPE_EXPOSURE_LIMITS.maximumObservations), maximumAllocations: z.number().int().positive().max(RECIPE_EXPOSURE_LIMITS.maximumAllocations), maximumInputCharacters: z.number().int().positive().max(RECIPE_EXPOSURE_LIMITS.maximumInputCharacters) }).strict(), }).strict(); } export const recipeExposureProjectionRequestSchema: z.ZodType = z.lazy(() => recipeExposureSchemaForPreparation(reviewRecipePreparationSchema)); export function parseRecipeExposureProjectionRequest(input: unknown): RecipeExposureProjectionRequest { return parseReviewBoundary(recipeExposureProjectionRequestSchema, input); } export function projectRecipeExposureFromInput(input: unknown): RecipeExposureProjection { return projectRecipeExposure(parseRecipeExposureProjectionRequest(input)); } export const attachRecipeExposureRequestSchema = z.lazy(() => z.object({ id: token, version: token, context: analysisContextSchema, snapshots: z.array(analysisSnapshotSchema), // Validate the request shape without replacing the supplied projection. Zod object // reconstruction drops __proto__; core must inspect that original field during replay. projection: z.unknown().superRefine((value, context) => { const parsed = z.object({ request: recipeExposureProjectionRequestSchema }).passthrough().safeParse(value); if (!parsed.success) for (const issue of parsed.error.issues) context.addIssue(issue); }), relationship: exposurePopulationRelationshipSchema }).strict()); export function parseAttachRecipeExposureRequest(input: unknown): AttachRecipeExposureRequest { // The retained projection remains untrusted until core compares every field with its replay. return parseReviewBoundary(attachRecipeExposureRequestSchema, input) as unknown as AttachRecipeExposureRequest; } export function attachRecipeExposureFromInput(input: unknown): AttachedRecipeExposure { return attachRecipeExposure(parseAttachRecipeExposureRequest(input)); }