import { Either, Schema } from "effect"; import type * as AST from "effect/SchemaAST"; import { workflowRuntimeContract, type WorkflowOperationParams, type WorkflowOperationResult, } from "../generated/workflow-contract.js"; import { translateJsonSchema } from "./translator.js"; import { hasBoundedInputExpressionsAtSchema } from "./input-expression-limits.js"; type RuntimeOperationName = keyof typeof workflowRuntimeContract.operations; const MAX_RUNTIME_VALUE_DEPTH = 64; type RuntimeValueFrame = | { readonly kind: "enter"; readonly depth: number; readonly value: unknown } | { readonly kind: "leave"; readonly value: object }; export interface RuntimeOperationSchemas { readonly payload: Schema.Schema, unknown, never>; readonly success: Schema.Schema, unknown, never>; } const hasBoundedRuntimeValueDepth = (input: unknown): boolean => { const pending: RuntimeValueFrame[] = [{ kind: "enter", depth: 0, value: input }]; // Track only the current ancestor chain so shared acyclic values remain valid. const active = new WeakSet(); while (pending.length > 0) { const current = pending.pop(); if (current === undefined) break; if (current.kind === "leave") { active.delete(current.value); continue; } if (typeof current.value !== "object" || current.value === null) continue; if (current.depth >= MAX_RUNTIME_VALUE_DEPTH) return false; if (active.has(current.value)) return false; active.add(current.value); pending.push({ kind: "leave", value: current.value }); for (const value of Object.values(current.value)) { pending.push({ kind: "enter", depth: current.depth + 1, value }); } } return true; }; const BoundedRuntimeValueSchema = Schema.Unknown.pipe( Schema.filter(hasBoundedRuntimeValueDepth, { message: () => `runtime value exceeds ${MAX_RUNTIME_VALUE_DEPTH} nested containers`, }), ); const translatedAst = (schema: unknown): AST.AST => { const translated = translateJsonSchema(schema, { components: workflowRuntimeContract.components, }); if (Either.isLeft(translated)) { throw new Error( `checked runtime schema failed at ${translated.left.path}: ${translated.left.message}`, ); } return translated.right.ast; }; const boundedRuntimeInputFor = (operationSchema: unknown) => BoundedRuntimeValueSchema.pipe( Schema.filter( (value) => hasBoundedInputExpressionsAtSchema( value, operationSchema, workflowRuntimeContract.components, ), { message: () => "runtime value contains an input expression over the 1024-node budget", }, ), ); const payloadSchemaFor = ( name: Name, ): Schema.Schema, unknown, never> => { // The AST and payload type are generated from the same checked operation. const schema = Schema.make, unknown, never>( translatedAst(workflowRuntimeContract.operations[name].payload), ); return Schema.compose( boundedRuntimeInputFor(workflowRuntimeContract.operations[name].payload), schema, ); }; const successSchemaFor = ( name: Name, ): Schema.Schema, unknown, never> => { // The AST and result type are generated from the same checked operation. const schema = Schema.make, unknown, never>( translatedAst(workflowRuntimeContract.operations[name].success), ); return Schema.compose( boundedRuntimeInputFor(workflowRuntimeContract.operations[name].success), schema, ); }; /** Returns fail-fast Effect schemas for one parity-verified authored RPC. */ export const runtimeSchemasFor = ( name: Name, ): RuntimeOperationSchemas => { return { payload: payloadSchemaFor(name), success: successSchemaFor(name), }; };