ai-workflows 2.1.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -1
  3. package/README.md +2 -0
  4. package/dist/barrier.d.ts +6 -0
  5. package/dist/barrier.d.ts.map +1 -1
  6. package/dist/barrier.js +45 -7
  7. package/dist/barrier.js.map +1 -1
  8. package/dist/cascade-context.d.ts.map +1 -1
  9. package/dist/cascade-context.js +25 -25
  10. package/dist/cascade-context.js.map +1 -1
  11. package/dist/cascade-executor.d.ts.map +1 -1
  12. package/dist/cascade-executor.js +1 -1
  13. package/dist/cascade-executor.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +23 -7
  16. package/dist/context.js.map +1 -1
  17. package/dist/cron-parser.d.ts +65 -0
  18. package/dist/cron-parser.d.ts.map +1 -0
  19. package/dist/cron-parser.js +294 -0
  20. package/dist/cron-parser.js.map +1 -0
  21. package/dist/cron-scheduler.d.ts +117 -0
  22. package/dist/cron-scheduler.d.ts.map +1 -0
  23. package/dist/cron-scheduler.js +176 -0
  24. package/dist/cron-scheduler.js.map +1 -0
  25. package/dist/database-context.d.ts +184 -0
  26. package/dist/database-context.d.ts.map +1 -0
  27. package/dist/database-context.js +428 -0
  28. package/dist/database-context.js.map +1 -0
  29. package/dist/digital-objects-adapter.d.ts +159 -0
  30. package/dist/digital-objects-adapter.d.ts.map +1 -0
  31. package/dist/digital-objects-adapter.js +229 -0
  32. package/dist/digital-objects-adapter.js.map +1 -0
  33. package/dist/durable-execution-cloudflare.d.ts +427 -0
  34. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  35. package/dist/durable-execution-cloudflare.js +510 -0
  36. package/dist/durable-execution-cloudflare.js.map +1 -0
  37. package/dist/durable-execution.d.ts +482 -0
  38. package/dist/durable-execution.d.ts.map +1 -0
  39. package/dist/durable-execution.js +594 -0
  40. package/dist/durable-execution.js.map +1 -0
  41. package/dist/durable-workflow.d.ts +176 -0
  42. package/dist/durable-workflow.d.ts.map +1 -0
  43. package/dist/durable-workflow.js +552 -0
  44. package/dist/durable-workflow.js.map +1 -0
  45. package/dist/graph/topological-sort.d.ts.map +1 -1
  46. package/dist/graph/topological-sort.js +5 -5
  47. package/dist/graph/topological-sort.js.map +1 -1
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +15 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/logger.d.ts +101 -0
  53. package/dist/logger.d.ts.map +1 -0
  54. package/dist/logger.js +115 -0
  55. package/dist/logger.js.map +1 -0
  56. package/dist/on.d.ts.map +1 -1
  57. package/dist/on.js +3 -3
  58. package/dist/on.js.map +1 -1
  59. package/dist/runtime.d.ts +169 -0
  60. package/dist/runtime.d.ts.map +1 -0
  61. package/dist/runtime.js +275 -0
  62. package/dist/runtime.js.map +1 -0
  63. package/dist/send.d.ts.map +1 -1
  64. package/dist/send.js +4 -3
  65. package/dist/send.js.map +1 -1
  66. package/dist/telemetry.d.ts +150 -0
  67. package/dist/telemetry.d.ts.map +1 -0
  68. package/dist/telemetry.js +388 -0
  69. package/dist/telemetry.js.map +1 -0
  70. package/dist/timer-registry.d.ts +25 -0
  71. package/dist/timer-registry.d.ts.map +1 -1
  72. package/dist/timer-registry.js +42 -8
  73. package/dist/timer-registry.js.map +1 -1
  74. package/dist/types.d.ts +17 -6
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +1 -1
  77. package/dist/types.js.map +1 -1
  78. package/dist/worker/durable-step.d.ts +481 -0
  79. package/dist/worker/durable-step.d.ts.map +1 -0
  80. package/dist/worker/durable-step.js +606 -0
  81. package/dist/worker/durable-step.js.map +1 -0
  82. package/dist/worker/index.d.ts +106 -0
  83. package/dist/worker/index.d.ts.map +1 -0
  84. package/dist/worker/index.js +124 -0
  85. package/dist/worker/index.js.map +1 -0
  86. package/dist/worker/state-adapter.d.ts +230 -0
  87. package/dist/worker/state-adapter.d.ts.map +1 -0
  88. package/dist/worker/state-adapter.js +409 -0
  89. package/dist/worker/state-adapter.js.map +1 -0
  90. package/dist/worker/topological-executor.d.ts +282 -0
  91. package/dist/worker/topological-executor.d.ts.map +1 -0
  92. package/dist/worker/topological-executor.js +396 -0
  93. package/dist/worker/topological-executor.js.map +1 -0
  94. package/dist/worker/workflow-builder.d.ts +286 -0
  95. package/dist/worker/workflow-builder.d.ts.map +1 -0
  96. package/dist/worker/workflow-builder.js +565 -0
  97. package/dist/worker/workflow-builder.js.map +1 -0
  98. package/dist/worker.d.ts +800 -0
  99. package/dist/worker.d.ts.map +1 -0
  100. package/dist/worker.js +2428 -0
  101. package/dist/worker.js.map +1 -0
  102. package/dist/workflow-builder.d.ts +287 -0
  103. package/dist/workflow-builder.d.ts.map +1 -0
  104. package/dist/workflow-builder.js +762 -0
  105. package/dist/workflow-builder.js.map +1 -0
  106. package/dist/workflow.d.ts +14 -30
  107. package/dist/workflow.d.ts.map +1 -1
  108. package/dist/workflow.js +132 -292
  109. package/dist/workflow.js.map +1 -1
  110. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  111. package/examples/02-content-moderation-cascade.ts +454 -0
  112. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  113. package/examples/04-database-persistence.ts +518 -0
  114. package/examples/README.md +173 -0
  115. package/package.json +30 -13
  116. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  117. package/src/__tests__/durable-workflow.test.ts +297 -0
  118. package/src/barrier.ts +48 -7
  119. package/src/cascade-context.ts +36 -29
  120. package/src/cascade-executor.ts +3 -2
  121. package/src/context.ts +41 -12
  122. package/src/cron-parser.ts +347 -0
  123. package/src/cron-scheduler.ts +239 -0
  124. package/src/database-context.ts +658 -0
  125. package/src/digital-objects-adapter.ts +351 -0
  126. package/src/durable-execution-cloudflare.ts +855 -0
  127. package/src/durable-execution.ts +1042 -0
  128. package/src/durable-workflow.ts +717 -0
  129. package/src/graph/topological-sort.ts +6 -8
  130. package/src/index.ts +69 -0
  131. package/src/logger.ts +148 -0
  132. package/src/on.ts +8 -9
  133. package/src/runtime.ts +436 -0
  134. package/src/send.ts +4 -5
  135. package/src/telemetry.ts +577 -0
  136. package/src/timer-registry.ts +44 -10
  137. package/src/types.ts +32 -17
  138. package/src/worker/durable-step.ts +976 -0
  139. package/src/worker/index.ts +216 -0
  140. package/src/worker/state-adapter.ts +589 -0
  141. package/src/worker/topological-executor.ts +625 -0
  142. package/src/worker/workflow-builder.ts +871 -0
  143. package/src/worker.ts +2906 -0
  144. package/src/workflow-builder.ts +1068 -0
  145. package/src/workflow.ts +188 -351
  146. package/test/barrier-join.test.ts +32 -24
  147. package/test/cascade-executor.test.ts +9 -16
  148. package/test/cron-parser.test.ts +314 -0
  149. package/test/cron-scheduler.test.ts +291 -0
  150. package/test/database-context.test.ts +770 -0
  151. package/test/db-provider-adapter.test.ts +862 -0
  152. package/test/durable-execution-cloudflare.test.ts +606 -0
  153. package/test/durable-execution-in-process.test.ts +286 -0
  154. package/test/durable-execution.test.ts +247 -0
  155. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  156. package/test/integration.test.ts +442 -0
  157. package/test/rpc-surface.test.ts +946 -0
  158. package/test/runtime.test.ts +262 -0
  159. package/test/schedule-timer-cleanup.test.ts +30 -21
  160. package/test/send-race-conditions.test.ts +30 -40
  161. package/test/worker/durable-cascade.test.ts +1117 -0
  162. package/test/worker/durable-step.test.ts +723 -0
  163. package/test/worker/topological-executor.test.ts +1240 -0
  164. package/test/worker/workflow-builder.test.ts +1067 -0
  165. package/test/worker.test.ts +608 -0
  166. package/test/workflow-builder.test.ts +1670 -0
  167. package/test/workflow-cron.test.ts +256 -0
  168. package/test/workflow-state-adapter.test.ts +923 -0
  169. package/test/workflow.test.ts +25 -22
  170. package/tsconfig.json +3 -1
  171. package/vitest.config.ts +38 -1
  172. package/vitest.workers.config.ts +44 -0
  173. package/wrangler.jsonc +22 -0
  174. package/.turbo/turbo-test.log +0 -169
  175. package/LICENSE +0 -21
  176. package/src/context.js +0 -83
  177. package/src/every.js +0 -267
  178. package/src/index.js +0 -71
  179. package/src/on.js +0 -79
  180. package/src/send.js +0 -111
  181. package/src/types.js +0 -4
  182. package/src/workflow.js +0 -455
  183. package/test/context.test.js +0 -116
  184. package/test/every.test.js +0 -282
  185. package/test/on.test.js +0 -80
  186. package/test/send.test.js +0 -89
  187. package/test/workflow.test.js +0 -224
  188. package/vitest.config.js +0 -7
@@ -0,0 +1,1117 @@
1
+ /**
2
+ * DurableStep.cascade() Tests (RED Phase)
3
+ *
4
+ * Tests for DurableStep.cascade() - durable tiered execution pattern that
5
+ * combines code -> generative -> agentic -> human escalation with
6
+ * Cloudflare Workflows durability guarantees.
7
+ *
8
+ * These tests define the expected behavior for DurableStep.cascade() before implementation.
9
+ * All tests SHOULD FAIL because DurableStep.cascade() does not exist yet.
10
+ *
11
+ * Uses @cloudflare/vitest-pool-workers - NO MOCKS.
12
+ * Tests run against real Cloudflare Workflows bindings with AI Gateway.
13
+ *
14
+ * Bead: aip-9390
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
19
+ import { describe, it, expect, beforeEach } from 'vitest'
20
+ import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'
21
+
22
+ // ============================================================================
23
+ // These imports will FAIL because DurableStep.cascade() is not yet implemented.
24
+ // This is the RED phase of TDD.
25
+ // ============================================================================
26
+ import {
27
+ DurableStep,
28
+ StepContext,
29
+ type StepConfig,
30
+ type WorkflowStep,
31
+ } from '../../src/worker/durable-step.js'
32
+
33
+ // Import cascade types - these will need to be defined in DurableStep
34
+ // These imports WILL FAIL because the types don't exist yet
35
+ import type {
36
+ CascadeConfig,
37
+ CascadeTierConfig,
38
+ CascadeTierResult,
39
+ CascadeResult,
40
+ CascadeContext,
41
+ DurableCascadeStep,
42
+ } from '../../src/worker/durable-step.js'
43
+
44
+ // Import the TestWorkflow that should be defined in worker.ts
45
+ import { TestWorkflow } from '../../src/worker.js'
46
+
47
+ // ============================================================================
48
+ // Type Definitions for Test Environment
49
+ // ============================================================================
50
+
51
+ interface TestEnv {
52
+ WORKFLOW: Workflow
53
+ AI: Ai
54
+ AI_GATEWAY: AiGateway
55
+ }
56
+
57
+ // ============================================================================
58
+ // Helper Functions
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Get a workflow instance from the binding.
63
+ */
64
+ async function getWorkflowInstance(name?: string): Promise<WorkflowInstance> {
65
+ const instance = await env.WORKFLOW.create({
66
+ id: name ?? crypto.randomUUID(),
67
+ })
68
+ return instance
69
+ }
70
+
71
+ /**
72
+ * Run a workflow and wait for it to complete.
73
+ */
74
+ async function runWorkflow<T>(instance: WorkflowInstance, params?: unknown): Promise<T> {
75
+ const status = await instance.status()
76
+ if (status.status === 'queued' || status.status === 'running') {
77
+ let current = status
78
+ while (current.status !== 'complete' && current.status !== 'errored') {
79
+ await new Promise((resolve) => setTimeout(resolve, 100))
80
+ current = await instance.status()
81
+ }
82
+ if (current.status === 'errored') {
83
+ const error = current.error as unknown
84
+ let errorMessage: string
85
+
86
+ if (typeof error === 'string') {
87
+ errorMessage = error
88
+ } else if (error && typeof error === 'object') {
89
+ const err = error as Record<string, unknown>
90
+ errorMessage = (err.message ??
91
+ err.name ??
92
+ err.error ??
93
+ (err.cause &&
94
+ typeof err.cause === 'object' &&
95
+ (err.cause as Record<string, unknown>).message) ??
96
+ JSON.stringify(error)) as string
97
+ } else {
98
+ errorMessage = String(error ?? 'Unknown error')
99
+ }
100
+ throw new Error(errorMessage)
101
+ }
102
+ return current.output as T
103
+ }
104
+ return status.output as T
105
+ }
106
+
107
+ // ============================================================================
108
+ // 1. DurableStep.cascade() - Static Method Existence
109
+ // ============================================================================
110
+
111
+ describe('DurableStep.cascade()', () => {
112
+ describe('static method existence', () => {
113
+ it('should expose DurableStep.cascade() as a static method', () => {
114
+ // DurableStep.cascade() should be a static factory method
115
+ expect(DurableStep.cascade).toBeDefined()
116
+ expect(typeof DurableStep.cascade).toBe('function')
117
+ })
118
+
119
+ it('should create a DurableCascadeStep with cascade configuration', () => {
120
+ const cascadeStep = DurableStep.cascade('process-refund', {
121
+ code: async (input) => ({ approved: true }),
122
+ })
123
+
124
+ expect(cascadeStep).toBeDefined()
125
+ expect(cascadeStep.name).toBe('process-refund')
126
+ })
127
+
128
+ it('should return a DurableCascadeStep that can be run with workflow step', async () => {
129
+ const cascadeStep = DurableStep.cascade('test-cascade', {
130
+ code: async (input) => ({ result: 'code' }),
131
+ })
132
+
133
+ // Should have a run method like regular DurableStep
134
+ expect(cascadeStep.run).toBeDefined()
135
+ expect(typeof cascadeStep.run).toBe('function')
136
+ })
137
+ })
138
+
139
+ // ============================================================================
140
+ // 2. Cascade Tier Definition
141
+ // ============================================================================
142
+
143
+ describe('cascade tier definition', () => {
144
+ it('should accept code tier handler', () => {
145
+ const cascadeStep = DurableStep.cascade('code-only', {
146
+ code: async (input: { amount: number }) => {
147
+ if (input.amount < 100) {
148
+ return { approved: true, reason: 'Auto-approved small amount' }
149
+ }
150
+ throw new Error('Amount too large for code tier')
151
+ },
152
+ })
153
+
154
+ expect(cascadeStep).toBeDefined()
155
+ })
156
+
157
+ it('should accept generative tier handler with AI context', () => {
158
+ const cascadeStep = DurableStep.cascade('with-generative', {
159
+ code: async (input) => {
160
+ throw new Error('Escalate to generative')
161
+ },
162
+ generative: async (input, ctx) => {
163
+ // ctx should have ai binding for AI Gateway
164
+ const result = await ctx.ai.run('@cf/meta/llama-3-8b-instruct', {
165
+ messages: [{ role: 'user', content: `Process: ${JSON.stringify(input)}` }],
166
+ })
167
+ return { decision: result.response }
168
+ },
169
+ })
170
+
171
+ expect(cascadeStep).toBeDefined()
172
+ })
173
+
174
+ it('should accept agentic tier handler with tools', () => {
175
+ const cascadeStep = DurableStep.cascade('with-agentic', {
176
+ code: async (input) => {
177
+ throw new Error('Escalate to agentic')
178
+ },
179
+ agentic: async (input, ctx) => {
180
+ // Agentic tier can use tools and make multiple AI calls
181
+ const analysis = await ctx.ai.run('@cf/meta/llama-3-8b-instruct', {
182
+ messages: [{ role: 'user', content: 'Analyze this request' }],
183
+ })
184
+ return { agentDecision: analysis.response }
185
+ },
186
+ })
187
+
188
+ expect(cascadeStep).toBeDefined()
189
+ })
190
+
191
+ it('should accept human tier handler for human-in-the-loop', () => {
192
+ const cascadeStep = DurableStep.cascade('with-human', {
193
+ code: async (input) => {
194
+ throw new Error('Escalate to human')
195
+ },
196
+ human: async (input, ctx) => {
197
+ // Human tier should create a human review request and wait
198
+ const reviewId = await ctx.requestHumanReview({
199
+ type: 'refund-approval',
200
+ data: input,
201
+ assignee: 'finance-team',
202
+ })
203
+ return { reviewId, status: 'pending-human' }
204
+ },
205
+ })
206
+
207
+ expect(cascadeStep).toBeDefined()
208
+ })
209
+
210
+ it('should accept all four tiers in a complete cascade', () => {
211
+ const cascadeStep = DurableStep.cascade('full-cascade', {
212
+ code: async (input) => {
213
+ // Try deterministic rules first
214
+ if (input.amount < 50) return { approved: true }
215
+ throw new Error('Needs AI review')
216
+ },
217
+ generative: async (input, ctx) => {
218
+ // Try simple AI decision
219
+ const result = await ctx.ai.run('@cf/meta/llama-3-8b-instruct', {
220
+ messages: [{ role: 'user', content: 'Should we approve?' }],
221
+ })
222
+ if (result.response.includes('yes')) return { approved: true }
223
+ throw new Error('Needs deeper analysis')
224
+ },
225
+ agentic: async (input, ctx) => {
226
+ // Try agentic reasoning with tools
227
+ const analysis = await ctx.ai.run('@cf/meta/llama-3-8b-instruct', {
228
+ messages: [{ role: 'user', content: 'Deep analysis needed' }],
229
+ })
230
+ if (analysis.response.includes('approve')) return { approved: true }
231
+ throw new Error('Needs human review')
232
+ },
233
+ human: async (input, ctx) => {
234
+ // Fall back to human
235
+ return ctx.requestHumanReview({ type: 'manual-review', data: input })
236
+ },
237
+ })
238
+
239
+ expect(cascadeStep).toBeDefined()
240
+ })
241
+ })
242
+
243
+ // ============================================================================
244
+ // 3. Cascade Execution Order
245
+ // ============================================================================
246
+
247
+ describe('cascade execution order', () => {
248
+ it('should execute tiers in order: code -> generative -> agentic -> human', async () => {
249
+ const instance = await getWorkflowInstance('cascade-order-test-1')
250
+
251
+ const result = await runWorkflow<{
252
+ executionOrder: string[]
253
+ finalTier: string
254
+ }>(instance)
255
+
256
+ expect(result.executionOrder).toEqual(['code', 'generative', 'agentic', 'human'])
257
+ expect(result.finalTier).toBe('human')
258
+ })
259
+
260
+ it('should short-circuit when a tier succeeds', async () => {
261
+ const instance = await getWorkflowInstance('cascade-shortcircuit-test-1')
262
+
263
+ const result = await runWorkflow<{
264
+ executedTiers: string[]
265
+ successTier: string
266
+ value: unknown
267
+ }>(instance)
268
+
269
+ // If code tier succeeds, should not execute other tiers
270
+ expect(result.executedTiers).toEqual(['code'])
271
+ expect(result.successTier).toBe('code')
272
+ })
273
+
274
+ it('should escalate to next tier on failure', async () => {
275
+ const instance = await getWorkflowInstance('cascade-escalate-test-1')
276
+
277
+ const result = await runWorkflow<{
278
+ executedTiers: string[]
279
+ successTier: string
280
+ errors: Array<{ tier: string; error: string }>
281
+ }>(instance)
282
+
283
+ // Code fails, generative succeeds
284
+ expect(result.executedTiers).toEqual(['code', 'generative'])
285
+ expect(result.successTier).toBe('generative')
286
+ expect(result.errors).toHaveLength(1)
287
+ expect(result.errors[0].tier).toBe('code')
288
+ })
289
+
290
+ it('should skip unconfigured tiers gracefully', async () => {
291
+ const instance = await getWorkflowInstance('cascade-skip-test-1')
292
+
293
+ const result = await runWorkflow<{
294
+ executedTiers: string[]
295
+ skippedTiers: string[]
296
+ successTier: string
297
+ }>(instance)
298
+
299
+ // Only code and human configured, generative and agentic skipped
300
+ expect(result.skippedTiers).toContain('generative')
301
+ expect(result.skippedTiers).toContain('agentic')
302
+ })
303
+ })
304
+
305
+ // ============================================================================
306
+ // 4. AI Tier -> Human Tier Fallback
307
+ // ============================================================================
308
+
309
+ describe('AI to human fallback', () => {
310
+ it('should escalate from generative to human when AI fails', async () => {
311
+ const instance = await getWorkflowInstance('ai-human-fallback-test-1')
312
+
313
+ const result = await runWorkflow<{
314
+ aiTierFailed: boolean
315
+ humanTierInvoked: boolean
316
+ finalResult: unknown
317
+ }>(instance)
318
+
319
+ expect(result.aiTierFailed).toBe(true)
320
+ expect(result.humanTierInvoked).toBe(true)
321
+ })
322
+
323
+ it('should preserve AI tier error context for human review', async () => {
324
+ const instance = await getWorkflowInstance('ai-error-context-test-1')
325
+
326
+ const result = await runWorkflow<{
327
+ humanReviewContext: {
328
+ previousTierErrors: Array<{
329
+ tier: string
330
+ error: string
331
+ attempt: number
332
+ }>
333
+ }
334
+ }>(instance)
335
+
336
+ expect(result.humanReviewContext.previousTierErrors).toBeDefined()
337
+ expect(result.humanReviewContext.previousTierErrors.length).toBeGreaterThan(0)
338
+ })
339
+
340
+ it('should include AI reasoning in human escalation', async () => {
341
+ const instance = await getWorkflowInstance('ai-reasoning-test-1')
342
+
343
+ const result = await runWorkflow<{
344
+ humanReviewData: {
345
+ aiAttempts: Array<{
346
+ tier: string
347
+ reasoning: string
348
+ confidence: number
349
+ }>
350
+ escalationReason: string
351
+ }
352
+ }>(instance)
353
+
354
+ expect(result.humanReviewData.aiAttempts).toBeDefined()
355
+ expect(result.humanReviewData.escalationReason).toBeDefined()
356
+ })
357
+
358
+ it('should support custom escalation conditions', async () => {
359
+ const instance = await getWorkflowInstance('custom-escalation-test-1')
360
+
361
+ // Cascade with custom condition: escalate if confidence < 0.8
362
+ const result = await runWorkflow<{
363
+ aiConfidence: number
364
+ escalatedDueToLowConfidence: boolean
365
+ humanInvoked: boolean
366
+ }>(instance)
367
+
368
+ expect(result.aiConfidence).toBeLessThan(0.8)
369
+ expect(result.escalatedDueToLowConfidence).toBe(true)
370
+ expect(result.humanInvoked).toBe(true)
371
+ })
372
+ })
373
+
374
+ // ============================================================================
375
+ // 5. Multiple AI Providers (Fast Model -> Slow Model)
376
+ // ============================================================================
377
+
378
+ describe('multiple AI providers cascade', () => {
379
+ it('should support fast model before slow model in generative tier', async () => {
380
+ const instance = await getWorkflowInstance('fast-slow-model-test-1')
381
+
382
+ const result = await runWorkflow<{
383
+ modelUsed: string
384
+ attemptedModels: string[]
385
+ response: string
386
+ }>(instance)
387
+
388
+ // Fast model should be tried first
389
+ expect(result.attemptedModels[0]).toBe('@cf/meta/llama-3-8b-instruct')
390
+ // If fast fails, slow model
391
+ expect(result.attemptedModels).toContain('@cf/meta/llama-3-70b-instruct')
392
+ })
393
+
394
+ it('should cascade through model tiers within generative tier', async () => {
395
+ const instance = await getWorkflowInstance('model-cascade-test-1')
396
+
397
+ const result = await runWorkflow<{
398
+ modelAttempts: Array<{
399
+ model: string
400
+ success: boolean
401
+ latencyMs: number
402
+ }>
403
+ finalModel: string
404
+ }>(instance)
405
+
406
+ expect(result.modelAttempts.length).toBeGreaterThan(1)
407
+ expect(result.finalModel).toBeDefined()
408
+ })
409
+
410
+ it('should support custom model ordering', async () => {
411
+ const instance = await getWorkflowInstance('custom-model-order-test-1')
412
+
413
+ const result = await runWorkflow<{
414
+ modelOrder: string[]
415
+ selectedModel: string
416
+ }>(instance)
417
+
418
+ // Custom order: cheapest -> balanced -> premium
419
+ expect(result.modelOrder).toEqual([
420
+ '@cf/meta/llama-3-8b-instruct',
421
+ '@cf/mistral/mistral-7b-instruct-v0.1',
422
+ '@cf/meta/llama-3-70b-instruct',
423
+ ])
424
+ })
425
+
426
+ it('should respect model-specific timeouts', async () => {
427
+ const instance = await getWorkflowInstance('model-timeout-test-1')
428
+
429
+ const result = await runWorkflow<{
430
+ modelResults: Array<{
431
+ model: string
432
+ timedOut: boolean
433
+ timeoutMs: number
434
+ }>
435
+ }>(instance)
436
+
437
+ // Fast model has shorter timeout than slow model
438
+ const fastModel = result.modelResults.find((m) => m.model.includes('8b'))
439
+ const slowModel = result.modelResults.find((m) => m.model.includes('70b'))
440
+
441
+ expect(fastModel?.timeoutMs).toBeLessThan(slowModel?.timeoutMs ?? 0)
442
+ })
443
+
444
+ it('should use AI Gateway caching for deterministic tests', async () => {
445
+ const instance = await getWorkflowInstance('ai-gateway-cache-test-1')
446
+
447
+ const result = await runWorkflow<{
448
+ cacheHit: boolean
449
+ cachedResponse: string
450
+ responseTime: number
451
+ }>(instance)
452
+
453
+ // Second request should hit cache
454
+ expect(result.cacheHit).toBe(true)
455
+ expect(result.responseTime).toBeLessThan(100) // Cached response should be fast
456
+ })
457
+ })
458
+
459
+ // ============================================================================
460
+ // 6. Tier Timeout Configuration
461
+ // ============================================================================
462
+
463
+ describe('tier timeout configuration', () => {
464
+ it('should support per-tier timeout configuration', async () => {
465
+ const instance = await getWorkflowInstance('tier-timeout-config-test-1')
466
+
467
+ const result = await runWorkflow<{
468
+ tierTimeouts: Record<string, number>
469
+ appliedTimeouts: Record<string, number>
470
+ }>(instance)
471
+
472
+ expect(result.tierTimeouts).toEqual({
473
+ code: 5000,
474
+ generative: 30000,
475
+ agentic: 300000,
476
+ human: 86400000,
477
+ })
478
+ })
479
+
480
+ it('should use default timeouts when not specified', async () => {
481
+ const instance = await getWorkflowInstance('default-timeout-test-1')
482
+
483
+ const result = await runWorkflow<{
484
+ usedDefaults: boolean
485
+ defaultTimeouts: Record<string, number>
486
+ }>(instance)
487
+
488
+ expect(result.usedDefaults).toBe(true)
489
+ expect(result.defaultTimeouts.code).toBe(5000)
490
+ expect(result.defaultTimeouts.generative).toBe(30000)
491
+ })
492
+
493
+ it('should escalate on tier timeout', async () => {
494
+ const instance = await getWorkflowInstance('timeout-escalation-test-1')
495
+
496
+ const result = await runWorkflow<{
497
+ timedOutTier: string
498
+ escalatedToTier: string
499
+ timeoutError: string
500
+ }>(instance)
501
+
502
+ expect(result.timedOutTier).toBe('code')
503
+ expect(result.escalatedToTier).toBe('generative')
504
+ expect(result.timeoutError).toContain('timeout')
505
+ })
506
+
507
+ it('should record timeout in tier result', async () => {
508
+ const instance = await getWorkflowInstance('timeout-record-test-1')
509
+
510
+ const result = await runWorkflow<{
511
+ tierResults: Array<{
512
+ tier: string
513
+ timedOut: boolean
514
+ duration: number
515
+ configuredTimeout: number
516
+ }>
517
+ }>(instance)
518
+
519
+ const timedOutTier = result.tierResults.find((t) => t.timedOut)
520
+ expect(timedOutTier).toBeDefined()
521
+ expect(timedOutTier?.duration).toBeGreaterThanOrEqual(timedOutTier?.configuredTimeout ?? 0)
522
+ })
523
+
524
+ it('should support total cascade timeout', async () => {
525
+ const instance = await getWorkflowInstance('total-timeout-test-1')
526
+
527
+ // Cascade with 60s total timeout
528
+ await expect(runWorkflow(instance)).rejects.toThrow(/cascade.*timeout/i)
529
+ })
530
+ })
531
+
532
+ // ============================================================================
533
+ // 7. Tier Success Conditions
534
+ // ============================================================================
535
+
536
+ describe('tier success conditions', () => {
537
+ it('should treat returned value as success', async () => {
538
+ const instance = await getWorkflowInstance('return-success-test-1')
539
+
540
+ const result = await runWorkflow<{
541
+ tierStatus: string
542
+ returnedValue: unknown
543
+ }>(instance)
544
+
545
+ expect(result.tierStatus).toBe('success')
546
+ expect(result.returnedValue).toBeDefined()
547
+ })
548
+
549
+ it('should treat thrown error as failure and escalate', async () => {
550
+ const instance = await getWorkflowInstance('throw-failure-test-1')
551
+
552
+ const result = await runWorkflow<{
553
+ failedTier: string
554
+ escalatedTo: string
555
+ error: string
556
+ }>(instance)
557
+
558
+ expect(result.failedTier).toBe('code')
559
+ expect(result.escalatedTo).toBe('generative')
560
+ })
561
+
562
+ it('should support custom success condition function', async () => {
563
+ const instance = await getWorkflowInstance('custom-success-test-1')
564
+
565
+ const result = await runWorkflow<{
566
+ tierResult: { confidence: number }
567
+ customConditionResult: boolean
568
+ finalStatus: string
569
+ }>(instance)
570
+
571
+ // Custom condition: success only if confidence > 0.9
572
+ expect(result.tierResult.confidence).toBeLessThan(0.9)
573
+ expect(result.customConditionResult).toBe(false)
574
+ expect(result.finalStatus).toBe('escalated')
575
+ })
576
+
577
+ it('should support partial success with escalation', async () => {
578
+ const instance = await getWorkflowInstance('partial-success-test-1')
579
+
580
+ const result = await runWorkflow<{
581
+ partialResult: { approved: boolean; confidence: number }
582
+ needsHumanReview: boolean
583
+ escalatedWithPartialResult: boolean
584
+ }>(instance)
585
+
586
+ // Tier returned a result but flagged for human review
587
+ expect(result.partialResult.approved).toBe(true)
588
+ expect(result.partialResult.confidence).toBeLessThan(0.8)
589
+ expect(result.needsHumanReview).toBe(true)
590
+ expect(result.escalatedWithPartialResult).toBe(true)
591
+ })
592
+
593
+ it('should support retry before escalation', async () => {
594
+ const instance = await getWorkflowInstance('retry-before-escalate-test-1')
595
+
596
+ const result = await runWorkflow<{
597
+ tierAttempts: number
598
+ maxRetries: number
599
+ finallyEscalated: boolean
600
+ }>(instance)
601
+
602
+ // Should retry 3 times before escalating
603
+ expect(result.tierAttempts).toBe(3)
604
+ expect(result.maxRetries).toBe(3)
605
+ expect(result.finallyEscalated).toBe(true)
606
+ })
607
+ })
608
+
609
+ // ============================================================================
610
+ // 8. Tier Result Merging
611
+ // ============================================================================
612
+
613
+ describe('tier result merging', () => {
614
+ it('should accumulate results from all attempted tiers', async () => {
615
+ const instance = await getWorkflowInstance('result-accumulate-test-1')
616
+
617
+ const result = await runWorkflow<{
618
+ allTierResults: Array<{
619
+ tier: string
620
+ result: unknown
621
+ status: string
622
+ }>
623
+ }>(instance)
624
+
625
+ expect(result.allTierResults.length).toBeGreaterThan(1)
626
+ expect(result.allTierResults.every((t) => t.tier && t.status)).toBe(true)
627
+ })
628
+
629
+ it('should merge partial results into final result', async () => {
630
+ const instance = await getWorkflowInstance('result-merge-test-1')
631
+
632
+ const result = await runWorkflow<{
633
+ mergedResult: {
634
+ codeAnalysis: unknown
635
+ aiRecommendation: unknown
636
+ humanDecision: unknown
637
+ }
638
+ contributingTiers: string[]
639
+ }>(instance)
640
+
641
+ // Each tier contributes to the final merged result
642
+ expect(result.mergedResult.codeAnalysis).toBeDefined()
643
+ expect(result.contributingTiers.length).toBeGreaterThan(1)
644
+ })
645
+
646
+ it('should provide access to individual tier results', async () => {
647
+ const instance = await getWorkflowInstance('individual-results-test-1')
648
+
649
+ const result = await runWorkflow<CascadeResult<unknown>>(instance)
650
+
651
+ expect(result.history).toBeDefined()
652
+ expect(Array.isArray(result.history)).toBe(true)
653
+ expect(result.history[0]).toHaveProperty('tier')
654
+ expect(result.history[0]).toHaveProperty('success')
655
+ expect(result.history[0]).toHaveProperty('duration')
656
+ })
657
+
658
+ it('should support custom result merger function', async () => {
659
+ const instance = await getWorkflowInstance('custom-merger-test-1')
660
+
661
+ const result = await runWorkflow<{
662
+ customMergedResult: {
663
+ consensus: string
664
+ sources: string[]
665
+ }
666
+ }>(instance)
667
+
668
+ expect(result.customMergedResult.consensus).toBeDefined()
669
+ expect(result.customMergedResult.sources.length).toBeGreaterThan(0)
670
+ })
671
+
672
+ it('should preserve tier metadata in results', async () => {
673
+ const instance = await getWorkflowInstance('tier-metadata-test-1')
674
+
675
+ const result = await runWorkflow<{
676
+ tierMetadata: Array<{
677
+ tier: string
678
+ startTime: number
679
+ endTime: number
680
+ latencyMs: number
681
+ attempts: number
682
+ }>
683
+ }>(instance)
684
+
685
+ expect(result.tierMetadata.every((m) => m.latencyMs >= 0)).toBe(true)
686
+ expect(result.tierMetadata.every((m) => m.attempts >= 1)).toBe(true)
687
+ })
688
+ })
689
+
690
+ // ============================================================================
691
+ // 9. Error Propagation Through Tiers
692
+ // ============================================================================
693
+
694
+ describe('error propagation through tiers', () => {
695
+ it('should propagate error to next tier with context', async () => {
696
+ const instance = await getWorkflowInstance('error-propagate-test-1')
697
+
698
+ const result = await runWorkflow<{
699
+ receivedErrors: Array<{
700
+ fromTier: string
701
+ error: string
702
+ }>
703
+ currentTier: string
704
+ }>(instance)
705
+
706
+ expect(result.receivedErrors.length).toBeGreaterThan(0)
707
+ expect(result.receivedErrors[0].fromTier).toBeDefined()
708
+ })
709
+
710
+ it('should accumulate errors from all failed tiers', async () => {
711
+ const instance = await getWorkflowInstance('error-accumulate-test-1')
712
+
713
+ const result = await runWorkflow<{
714
+ allErrors: Array<{
715
+ tier: string
716
+ error: string
717
+ timestamp: number
718
+ }>
719
+ totalFailures: number
720
+ }>(instance)
721
+
722
+ expect(result.allErrors.length).toBe(result.totalFailures)
723
+ })
724
+
725
+ it('should throw AllTiersFailedError when all tiers fail', async () => {
726
+ const instance = await getWorkflowInstance('all-tiers-fail-test-1')
727
+
728
+ await expect(runWorkflow(instance)).rejects.toThrow(/all.*tiers.*failed/i)
729
+ })
730
+
731
+ it('should include error history in AllTiersFailedError', async () => {
732
+ const instance = await getWorkflowInstance('error-history-test-1')
733
+
734
+ try {
735
+ await runWorkflow(instance)
736
+ expect.fail('Should have thrown')
737
+ } catch (error: unknown) {
738
+ const err = error as Error & { history?: Array<{ tier: string; error: Error }> }
739
+ expect(err.history).toBeDefined()
740
+ expect(err.history?.length).toBeGreaterThan(0)
741
+ }
742
+ })
743
+
744
+ it('should support custom error handlers per tier', async () => {
745
+ const instance = await getWorkflowInstance('custom-error-handler-test-1')
746
+
747
+ const result = await runWorkflow<{
748
+ errorHandled: boolean
749
+ handlerTier: string
750
+ recoveredValue: unknown
751
+ }>(instance)
752
+
753
+ expect(result.errorHandled).toBe(true)
754
+ expect(result.recoveredValue).toBeDefined()
755
+ })
756
+
757
+ it('should support error transformation between tiers', async () => {
758
+ const instance = await getWorkflowInstance('error-transform-test-1')
759
+
760
+ const result = await runWorkflow<{
761
+ originalError: string
762
+ transformedError: string
763
+ transformedForHuman: boolean
764
+ }>(instance)
765
+
766
+ // Technical error transformed to human-readable
767
+ expect(result.originalError).toContain('ECONNREFUSED')
768
+ expect(result.transformedError).toContain('service temporarily unavailable')
769
+ })
770
+ })
771
+
772
+ // ============================================================================
773
+ // 10. Durable Cascade Checkpoints
774
+ // ============================================================================
775
+
776
+ describe('durable cascade checkpoints', () => {
777
+ it('should persist tier results as durable checkpoints', async () => {
778
+ const instance = await getWorkflowInstance('durable-checkpoint-test-1')
779
+
780
+ const result = await runWorkflow<{
781
+ checkpointsCreated: number
782
+ checkpointIds: string[]
783
+ }>(instance)
784
+
785
+ expect(result.checkpointsCreated).toBeGreaterThan(0)
786
+ expect(result.checkpointIds.length).toBe(result.checkpointsCreated)
787
+ })
788
+
789
+ it('should resume cascade from last successful tier on restart', async () => {
790
+ const instance = await getWorkflowInstance('cascade-resume-test-1')
791
+
792
+ const result = await runWorkflow<{
793
+ resumedFromTier: string
794
+ tiersReExecuted: string[]
795
+ tiersSkipped: string[]
796
+ }>(instance)
797
+
798
+ // Should not re-execute already completed tiers
799
+ expect(result.tiersSkipped.length).toBeGreaterThan(0)
800
+ })
801
+
802
+ it('should store tier inputs and outputs durably', async () => {
803
+ const instance = await getWorkflowInstance('durable-io-test-1')
804
+
805
+ const result = await runWorkflow<{
806
+ storedTierData: Array<{
807
+ tier: string
808
+ input: unknown
809
+ output: unknown
810
+ storedAt: number
811
+ }>
812
+ }>(instance)
813
+
814
+ expect(result.storedTierData.every((d) => d.input !== undefined)).toBe(true)
815
+ expect(result.storedTierData.every((d) => d.output !== undefined)).toBe(true)
816
+ })
817
+
818
+ it('should support snapshot and restore of cascade state', async () => {
819
+ const instance = await getWorkflowInstance('cascade-snapshot-test-1')
820
+
821
+ const result = await runWorkflow<{
822
+ snapshotId: string
823
+ restoredFromSnapshot: boolean
824
+ stateAfterRestore: {
825
+ currentTier: string
826
+ completedTiers: string[]
827
+ }
828
+ }>(instance)
829
+
830
+ expect(result.snapshotId).toBeDefined()
831
+ expect(result.restoredFromSnapshot).toBe(true)
832
+ })
833
+ })
834
+
835
+ // ============================================================================
836
+ // 11. 5W+H Audit Trail
837
+ // ============================================================================
838
+
839
+ describe('5W+H audit trail', () => {
840
+ it('should record Who (actor) for each tier execution', async () => {
841
+ const instance = await getWorkflowInstance('audit-who-test-1')
842
+
843
+ const result = await runWorkflow<{
844
+ auditEvents: Array<{
845
+ who: string
846
+ tier: string
847
+ }>
848
+ }>(instance)
849
+
850
+ expect(result.auditEvents.every((e) => e.who !== undefined)).toBe(true)
851
+ // Different actors for different tiers
852
+ expect(result.auditEvents.some((e) => e.who === 'system')).toBe(true)
853
+ expect(
854
+ result.auditEvents.some((e) => e.who.includes('human') || e.who.includes('user'))
855
+ ).toBe(true)
856
+ })
857
+
858
+ it('should record What (action) for each tier', async () => {
859
+ const instance = await getWorkflowInstance('audit-what-test-1')
860
+
861
+ const result = await runWorkflow<{
862
+ auditEvents: Array<{
863
+ what: string
864
+ tier: string
865
+ }>
866
+ }>(instance)
867
+
868
+ expect(result.auditEvents.find((e) => e.tier === 'code')?.what).toContain('execute')
869
+ expect(result.auditEvents.find((e) => e.tier === 'generative')?.what).toContain('ai')
870
+ })
871
+
872
+ it('should record When (timestamp) for all events', async () => {
873
+ const instance = await getWorkflowInstance('audit-when-test-1')
874
+
875
+ const result = await runWorkflow<{
876
+ auditEvents: Array<{
877
+ when: number
878
+ tier: string
879
+ }>
880
+ }>(instance)
881
+
882
+ // Events should be in chronological order
883
+ const timestamps = result.auditEvents.map((e) => e.when)
884
+ const sorted = [...timestamps].sort((a, b) => a - b)
885
+ expect(timestamps).toEqual(sorted)
886
+ })
887
+
888
+ it('should record Where (cascade context) for each event', async () => {
889
+ const instance = await getWorkflowInstance('audit-where-test-1')
890
+
891
+ const result = await runWorkflow<{
892
+ auditEvents: Array<{
893
+ where: string
894
+ cascadeName: string
895
+ workflowId: string
896
+ }>
897
+ }>(instance)
898
+
899
+ expect(result.auditEvents.every((e) => e.where === result.auditEvents[0].where)).toBe(true)
900
+ expect(result.auditEvents[0].cascadeName).toBeDefined()
901
+ })
902
+
903
+ it('should record Why (reason) for escalations', async () => {
904
+ const instance = await getWorkflowInstance('audit-why-test-1')
905
+
906
+ const result = await runWorkflow<{
907
+ escalationEvents: Array<{
908
+ why: string
909
+ fromTier: string
910
+ toTier: string
911
+ }>
912
+ }>(instance)
913
+
914
+ expect(result.escalationEvents.length).toBeGreaterThan(0)
915
+ expect(result.escalationEvents.every((e) => e.why !== undefined && e.why !== '')).toBe(true)
916
+ })
917
+
918
+ it('should record How (method/details) for tier execution', async () => {
919
+ const instance = await getWorkflowInstance('audit-how-test-1')
920
+
921
+ const result = await runWorkflow<{
922
+ auditEvents: Array<{
923
+ how: {
924
+ status: string
925
+ duration: number
926
+ metadata: Record<string, unknown>
927
+ }
928
+ }>
929
+ }>(instance)
930
+
931
+ expect(result.auditEvents.every((e) => e.how.status !== undefined)).toBe(true)
932
+ expect(result.auditEvents.every((e) => e.how.duration >= 0)).toBe(true)
933
+ })
934
+
935
+ it('should persist audit trail durably', async () => {
936
+ const instance = await getWorkflowInstance('audit-persist-test-1')
937
+
938
+ const result = await runWorkflow<{
939
+ auditTrailPersisted: boolean
940
+ auditRecordCount: number
941
+ canQueryAuditHistory: boolean
942
+ }>(instance)
943
+
944
+ expect(result.auditTrailPersisted).toBe(true)
945
+ expect(result.auditRecordCount).toBeGreaterThan(0)
946
+ expect(result.canQueryAuditHistory).toBe(true)
947
+ })
948
+ })
949
+
950
+ // ============================================================================
951
+ // 12. AI Gateway Integration
952
+ // ============================================================================
953
+
954
+ describe('AI Gateway integration', () => {
955
+ it('should use AI Gateway binding for generative tier', async () => {
956
+ const instance = await getWorkflowInstance('ai-gateway-binding-test-1')
957
+
958
+ const result = await runWorkflow<{
959
+ usedAiGateway: boolean
960
+ gatewayResponse: unknown
961
+ }>(instance)
962
+
963
+ expect(result.usedAiGateway).toBe(true)
964
+ expect(result.gatewayResponse).toBeDefined()
965
+ })
966
+
967
+ it('should support AI Gateway caching for deterministic tests', async () => {
968
+ const instance = await getWorkflowInstance('ai-gateway-caching-test-1')
969
+
970
+ const result = await runWorkflow<{
971
+ firstCallCached: boolean
972
+ secondCallFromCache: boolean
973
+ responsesMatch: boolean
974
+ }>(instance)
975
+
976
+ // First call should cache, second should hit cache
977
+ expect(result.secondCallFromCache).toBe(true)
978
+ expect(result.responsesMatch).toBe(true)
979
+ })
980
+
981
+ it('should provide AI context to tier handlers', async () => {
982
+ const instance = await getWorkflowInstance('ai-context-test-1')
983
+
984
+ const result = await runWorkflow<{
985
+ contextHasAi: boolean
986
+ aiBindingType: string
987
+ }>(instance)
988
+
989
+ expect(result.contextHasAi).toBe(true)
990
+ expect(result.aiBindingType).toBe('function')
991
+ })
992
+
993
+ it('should handle AI Gateway errors gracefully', async () => {
994
+ const instance = await getWorkflowInstance('ai-gateway-error-test-1')
995
+
996
+ const result = await runWorkflow<{
997
+ aiGatewayFailed: boolean
998
+ escalatedAfterAiFailure: boolean
999
+ errorCaptured: string
1000
+ }>(instance)
1001
+
1002
+ expect(result.aiGatewayFailed).toBe(true)
1003
+ expect(result.escalatedAfterAiFailure).toBe(true)
1004
+ })
1005
+ })
1006
+
1007
+ // ============================================================================
1008
+ // 13. Integration with Existing CascadeExecutor
1009
+ // ============================================================================
1010
+
1011
+ describe('CascadeExecutor integration', () => {
1012
+ it('should use CascadeContext for tracing', async () => {
1013
+ const instance = await getWorkflowInstance('cascade-context-test-1')
1014
+
1015
+ const result = await runWorkflow<{
1016
+ cascadeContext: {
1017
+ correlationId: string
1018
+ steps: Array<{ name: string; status: string }>
1019
+ }
1020
+ }>(instance)
1021
+
1022
+ expect(result.cascadeContext.correlationId).toBeDefined()
1023
+ expect(result.cascadeContext.steps.length).toBeGreaterThan(0)
1024
+ })
1025
+
1026
+ it('should emit FiveWH events via onEvent callback', async () => {
1027
+ const instance = await getWorkflowInstance('fivewh-events-test-1')
1028
+
1029
+ const result = await runWorkflow<{
1030
+ eventsEmitted: number
1031
+ eventTypes: string[]
1032
+ }>(instance)
1033
+
1034
+ expect(result.eventsEmitted).toBeGreaterThan(0)
1035
+ expect(result.eventTypes).toContain('tier-code-execute')
1036
+ })
1037
+
1038
+ it('should provide execution metrics', async () => {
1039
+ const instance = await getWorkflowInstance('metrics-test-1')
1040
+
1041
+ const result = await runWorkflow<{
1042
+ metrics: {
1043
+ totalDuration: number
1044
+ tierDurations: Record<string, number>
1045
+ }
1046
+ }>(instance)
1047
+
1048
+ expect(result.metrics.totalDuration).toBeGreaterThan(0)
1049
+ expect(Object.keys(result.metrics.tierDurations).length).toBeGreaterThan(0)
1050
+ })
1051
+ })
1052
+ })
1053
+
1054
+ // ============================================================================
1055
+ // 14. CascadeConfig Type Tests (Compile-time)
1056
+ // ============================================================================
1057
+
1058
+ describe('CascadeConfig types', () => {
1059
+ it('should define CascadeTierConfig type', () => {
1060
+ // This is a compile-time type test
1061
+ const tierConfig: CascadeTierConfig = {
1062
+ timeout: 5000,
1063
+ retries: { limit: 3, delay: 1000, backoff: 'exponential' },
1064
+ successCondition: (result) => result.confidence > 0.9,
1065
+ }
1066
+
1067
+ expect(tierConfig.timeout).toBe(5000)
1068
+ })
1069
+
1070
+ it('should define CascadeConfig with all tiers', () => {
1071
+ const config: CascadeConfig<{ approved: boolean }> = {
1072
+ code: async (input) => ({ approved: true }),
1073
+ generative: async (input, ctx) => ({ approved: true }),
1074
+ agentic: async (input, ctx) => ({ approved: true }),
1075
+ human: async (input, ctx) => ({ approved: true }),
1076
+ timeouts: {
1077
+ code: 5000,
1078
+ generative: 30000,
1079
+ agentic: 300000,
1080
+ human: 86400000,
1081
+ },
1082
+ totalTimeout: 100000,
1083
+ onEvent: (event) => console.log(event),
1084
+ resultMerger: (results) => results[results.length - 1]?.value ?? { approved: false },
1085
+ }
1086
+
1087
+ expect(config.code).toBeDefined()
1088
+ expect(config.timeouts?.code).toBe(5000)
1089
+ })
1090
+
1091
+ it('should define CascadeResult type', () => {
1092
+ const result: CascadeResult<{ approved: boolean }> = {
1093
+ value: { approved: true },
1094
+ tier: 'code',
1095
+ history: [
1096
+ {
1097
+ tier: 'code',
1098
+ success: true,
1099
+ value: { approved: true },
1100
+ duration: 100,
1101
+ },
1102
+ ],
1103
+ skippedTiers: ['generative', 'agentic', 'human'],
1104
+ context: {
1105
+ correlationId: 'test-123',
1106
+ steps: [],
1107
+ } as CascadeContext,
1108
+ metrics: {
1109
+ totalDuration: 100,
1110
+ tierDurations: { code: 100 },
1111
+ },
1112
+ }
1113
+
1114
+ expect(result.value.approved).toBe(true)
1115
+ expect(result.tier).toBe('code')
1116
+ })
1117
+ })