/** * POST /api/workflows/simulate * Dry-run simulation - offloaded entirely to external compute service * CF Worker just proxies the request and stores results in KV */ import { Hono } from 'hono'; import { z } from 'zod'; import type { Env } from '../types/env'; import type { WorkflowJob } from '../types/workflow'; import { createAuditEvent } from '../utils/audit'; export const simulateWorkflowRoute = new Hono<{ Bindings: Env }>(); const SimulateRequestSchema = z.object({ jobId: z.string().min(1), mockData: z.record(z.unknown()).optional(), }); simulateWorkflowRoute.post('/simulate', async (c) => { const body = await c.req.json().catch(() => null); if (!body) return c.json({ success: false, error: 'Invalid JSON body' }, 400); const parsed = SimulateRequestSchema.safeParse(body); if (!parsed.success) { return c.json({ success: false, error: 'Validation failed', details: parsed.error.flatten() }, 400); } const { jobId, mockData } = parsed.data; const raw = await c.env.WFO_CACHE.get(`job:${jobId}`, 'text'); if (!raw) return c.json({ success: false, error: `Job ${jobId} not found` }, 404); const job = JSON.parse(raw) as WorkflowJob; if (!job.compiledWorkflow || !job.graph) { return c.json({ success: false, error: 'Workflow not compiled or validated. Run /generate and /validate first.', }, 409); } if (job.validationReport && !job.validationReport.valid) { return c.json({ success: false, error: 'Cannot simulate an invalid workflow. Fix validation issues first.', validationReport: job.validationReport, }, 409); } // Offload simulation to external compute (CPU-intensive) const resp = await fetch(`${c.env.SIMULATOR_URL}/process/simulate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Internal-Secret': c.env.INTERNAL_API_SECRET, }, body: JSON.stringify({ jobId, workflow: job.compiledWorkflow, graph: job.graph, architecturePlan: job.architecturePlan, mockData: mockData ?? {}, }), signal: AbortSignal.timeout(25000), }).catch((e) => { throw new Error(`Simulator error: ${e instanceof Error ? e.message : 'unknown'}`); }); if (!resp.ok) { return c.json({ success: false, error: `Simulator returned ${resp.status}` }, 502); } const result = await resp.json() as { simulationReport: WorkflowJob['simulationReport'] }; const updated: WorkflowJob = { ...job, state: result.simulationReport?.passed ? 'simulated' : 'validated', simulationReport: result.simulationReport, updatedAt: new Date().toISOString(), auditLog: [ ...job.auditLog, createAuditEvent(jobId, 'simulation.completed', 'system', { passed: result.simulationReport?.passed, readinessScore: result.simulationReport?.readinessScore, }), ], }; await c.env.WFO_CACHE.put(`job:${jobId}`, JSON.stringify(updated), { expirationTtl: 86400 * 7 }); return c.json({ success: true, jobId, simulationReport: result.simulationReport }); });