@ryanfw/prompt-orchestration-pipeline 1.2.6 → 1.2.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryanfw/prompt-orchestration-pipeline",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "A Prompt-orchestration pipeline (POP) is a framework for building, running, and experimenting with complex chains of LLM tasks.",
5
5
  "type": "module",
6
6
  "main": "src/ui/server/index.ts",
@@ -15,7 +15,7 @@ import {
15
15
  } from "../models";
16
16
  import type { ModelConfigEntry } from "../models";
17
17
 
18
- const MODEL_COUNT = 41;
18
+ const MODEL_COUNT = 48;
19
19
  const PROVIDER_COUNT = 8;
20
20
 
21
21
  describe("ModelAlias", () => {
@@ -110,8 +110,8 @@ describe("DEFAULT_MODEL_BY_PROVIDER", () => {
110
110
  });
111
111
 
112
112
  describe("aliasToFunctionName", () => {
113
- it('converts "openai:gpt-5.2" to "gpt52"', () => {
114
- expect(aliasToFunctionName("openai:gpt-5.2")).toBe("gpt52");
113
+ it('converts "openai:gpt-5.4" to "gpt54"', () => {
114
+ expect(aliasToFunctionName("openai:gpt-5.4")).toBe("gpt54");
115
115
  });
116
116
 
117
117
  it('converts "gemini:flash-2.5-lite" to "flash25Lite"', () => {
@@ -137,8 +137,8 @@ describe("aliasToFunctionName", () => {
137
137
  });
138
138
 
139
139
  describe("getProviderFromAlias", () => {
140
- it('returns "openai" for "openai:gpt-5.2"', () => {
141
- expect(getProviderFromAlias("openai:gpt-5.2")).toBe("openai");
140
+ it('returns "openai" for "openai:gpt-5.4"', () => {
141
+ expect(getProviderFromAlias("openai:gpt-5.4")).toBe("openai");
142
142
  });
143
143
 
144
144
  it("throws for non-string input", () => {
@@ -152,8 +152,8 @@ describe("getProviderFromAlias", () => {
152
152
  });
153
153
 
154
154
  describe("getModelFromAlias", () => {
155
- it('returns "gpt-5.2" for "openai:gpt-5.2"', () => {
156
- expect(getModelFromAlias("openai:gpt-5.2")).toBe("gpt-5.2");
155
+ it('returns "gpt-5.4" for "openai:gpt-5.4"', () => {
156
+ expect(getModelFromAlias("openai:gpt-5.4")).toBe("gpt-5.4");
157
157
  });
158
158
 
159
159
  it("handles multiple colons by rejoining segments after first", () => {
@@ -171,8 +171,8 @@ describe("getModelFromAlias", () => {
171
171
  });
172
172
 
173
173
  describe("getModelConfig", () => {
174
- it('returns config for "openai:gpt-5.2" with provider "openai"', () => {
175
- const config = getModelConfig("openai:gpt-5.2");
174
+ it('returns config for "openai:gpt-5.4" with provider "openai"', () => {
175
+ const config = getModelConfig("openai:gpt-5.4");
176
176
  expect(config).not.toBeNull();
177
177
  expect(config!.provider).toBe("openai");
178
178
  });
@@ -184,10 +184,20 @@ describe("getModelConfig", () => {
184
184
 
185
185
  it("returns null for removed aliases", () => {
186
186
  expect(getModelConfig("openai:gpt-4")).toBeNull();
187
+ expect(getModelConfig("openai:gpt-4o")).toBeNull();
188
+ expect(getModelConfig("openai:gpt-4o-mini")).toBeNull();
189
+ expect(getModelConfig("openai:gpt-5.2")).toBeNull();
190
+ expect(getModelConfig("openai:gpt-5.2-pro")).toBeNull();
191
+ expect(getModelConfig("openai:o3-mini")).toBeNull();
192
+ expect(getModelConfig("anthropic:haiku-4-6")).toBeNull();
193
+ expect(getModelConfig("deepseek:deepseek-chat")).toBeNull();
194
+ expect(getModelConfig("deepseek:deepseek-reasoner")).toBeNull();
187
195
  expect(getModelConfig("deepseek:r1")).toBeNull();
196
+ expect(getModelConfig("moonshot:kimi-k1.5")).toBeNull();
188
197
  expect(getModelConfig("moonshot:kimi-moonshot-v1-128k")).toBeNull();
189
198
  expect(getModelConfig("gemini:flash-3")).toBeNull();
190
199
  expect(getModelConfig("gemini:pro-3")).toBeNull();
200
+ expect(getModelConfig("zai:glm-5-code")).toBeNull();
191
201
  expect(getModelConfig("zai:glm-4-plus")).toBeNull();
192
202
  });
193
203
  });
@@ -201,8 +211,8 @@ describe("FUNCTION_NAME_BY_ALIAS", () => {
201
211
  expect(Object.isFrozen(FUNCTION_NAME_BY_ALIAS)).toBe(true);
202
212
  });
203
213
 
204
- it("has correct value for openai:gpt-5.2", () => {
205
- expect(FUNCTION_NAME_BY_ALIAS["openai:gpt-5.2"]).toBe("gpt52");
214
+ it("has correct value for openai:gpt-5.4", () => {
215
+ expect(FUNCTION_NAME_BY_ALIAS["openai:gpt-5.4"]).toBe("gpt54");
206
216
  });
207
217
 
208
218
  it("has correct value for gemini:flash-2.5-lite", () => {
@@ -41,12 +41,12 @@ export const ModelAlias = Object.freeze({
41
41
  OPENAI_GPT_4_1: "openai:gpt-4-1",
42
42
  OPENAI_GPT_4_1_MINI: "openai:gpt-4-1-mini",
43
43
  OPENAI_GPT_4_1_NANO: "openai:gpt-4-1-nano",
44
- OPENAI_GPT_4O: "openai:gpt-4o",
45
- OPENAI_GPT_4O_MINI: "openai:gpt-4o-mini",
46
- OPENAI_GPT_5_2: "openai:gpt-5.2",
47
- OPENAI_GPT_5_2_PRO: "openai:gpt-5.2-pro",
44
+ OPENAI_GPT_5_4: "openai:gpt-5.4",
45
+ OPENAI_GPT_5_4_MINI: "openai:gpt-5.4-mini",
46
+ OPENAI_GPT_5_4_NANO: "openai:gpt-5.4-nano",
47
+ OPENAI_GPT_5_5: "openai:gpt-5.5",
48
+ OPENAI_GPT_5_5_PRO: "openai:gpt-5.5-pro",
48
49
  OPENAI_O3: "openai:o3",
49
- OPENAI_O3_MINI: "openai:o3-mini",
50
50
  OPENAI_O4_MINI: "openai:o4-mini",
51
51
  // Anthropic
52
52
  ANTHROPIC_OPUS_4_5: "anthropic:opus-4-5",
@@ -54,33 +54,40 @@ export const ModelAlias = Object.freeze({
54
54
  ANTHROPIC_HAIKU_4_5: "anthropic:haiku-4-5",
55
55
  ANTHROPIC_OPUS_4_6: "anthropic:opus-4-6",
56
56
  ANTHROPIC_SONNET_4_6: "anthropic:sonnet-4-6",
57
- ANTHROPIC_HAIKU_4_6: "anthropic:haiku-4-6",
57
+ ANTHROPIC_OPUS_4_7: "anthropic:opus-4-7",
58
58
  // Gemini
59
59
  GEMINI_FLASH_2_5: "gemini:flash-2.5",
60
60
  GEMINI_FLASH_2_5_LITE: "gemini:flash-2.5-lite",
61
61
  GEMINI_PRO_2_5: "gemini:pro-2.5",
62
- // DeepSeek — cache miss prices
63
- DEEPSEEK_CHAT: "deepseek:deepseek-chat",
64
- DEEPSEEK_REASONER: "deepseek:deepseek-reasoner",
62
+ GEMINI_PRO_3_1_PREVIEW: "gemini:pro-3.1-preview",
63
+ GEMINI_FLASH_3_PREVIEW: "gemini:flash-3-preview",
64
+ GEMINI_FLASH_3_1_LITE_PREVIEW: "gemini:flash-3.1-lite-preview",
65
+ // DeepSeek
66
+ DEEPSEEK_V4_FLASH: "deepseek:v4-flash",
67
+ DEEPSEEK_V4_PRO: "deepseek:v4-pro",
65
68
  // Moonshot / Kimi
66
69
  MOONSHOT_KIMI_K2_5: "moonshot:kimi-k2.5",
67
- MOONSHOT_KIMI_K1_5: "moonshot:kimi-k1.5",
70
+ MOONSHOT_KIMI_K2_6: "moonshot:kimi-k2.6",
68
71
  // Claude Code — subscription-based, zero token cost
69
72
  CLAUDE_CODE_SONNET: "claude-code:sonnet",
70
73
  CLAUDE_CODE_OPUS: "claude-code:opus",
71
74
  CLAUDE_CODE_HAIKU: "claude-code:haiku",
72
75
  // Z.ai
76
+ ZAI_GLM_5_1: "zai:glm-5-1",
73
77
  ZAI_GLM_5: "zai:glm-5",
74
- ZAI_GLM_5_CODE: "zai:glm-5-code",
78
+ ZAI_GLM_5_TURBO: "zai:glm-5-turbo",
75
79
  ZAI_GLM_4_7: "zai:glm-4-7",
76
80
  ZAI_GLM_4_7_FLASH_X: "zai:glm-4-7-flash-x",
77
81
  ZAI_GLM_4_6: "zai:glm-4-6",
78
82
  ZAI_GLM_4_5: "zai:glm-4-5",
83
+ ZAI_GLM_4_5_X: "zai:glm-4-5-x",
79
84
  ZAI_GLM_4_5_AIR: "zai:glm-4-5-air",
80
85
  ZAI_GLM_4_5_AIR_X: "zai:glm-4-5-air-x",
81
- // Alibaba (Qwen via DashScope)
86
+ // Alibaba (Qwen via DashScope, international/Singapore deployment)
82
87
  ALIBABA_QWEN3_MAX: "alibaba:qwen3-max",
88
+ ALIBABA_QWEN3_6_PLUS: "alibaba:qwen3.6-plus",
83
89
  ALIBABA_QWEN3_5_PLUS: "alibaba:qwen3.5-plus",
90
+ ALIBABA_QWEN3_5_FLASH: "alibaba:qwen3.5-flash",
84
91
  ALIBABA_QWEN_PLUS: "alibaba:qwen-plus",
85
92
  ALIBABA_QWEN_FLASH: "alibaba:qwen-flash",
86
93
  ALIBABA_QWQ_PLUS: "alibaba:qwq-plus",
@@ -112,41 +119,41 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
112
119
  tokenCostInPerMillion: 0.1,
113
120
  tokenCostOutPerMillion: 0.4,
114
121
  },
115
- "openai:gpt-4o": {
122
+ "openai:gpt-5.4": {
116
123
  provider: "openai",
117
- model: "gpt-4o",
124
+ model: "gpt-5.4",
118
125
  tokenCostInPerMillion: 2.5,
119
- tokenCostOutPerMillion: 10,
126
+ tokenCostOutPerMillion: 15,
120
127
  },
121
- "openai:gpt-4o-mini": {
128
+ "openai:gpt-5.4-mini": {
122
129
  provider: "openai",
123
- model: "gpt-4o-mini",
124
- tokenCostInPerMillion: 0.15,
125
- tokenCostOutPerMillion: 0.6,
130
+ model: "gpt-5.4-mini",
131
+ tokenCostInPerMillion: 0.75,
132
+ tokenCostOutPerMillion: 4.5,
126
133
  },
127
- "openai:gpt-5.2": {
134
+ "openai:gpt-5.4-nano": {
128
135
  provider: "openai",
129
- model: "gpt-5.2",
130
- tokenCostInPerMillion: 2,
131
- tokenCostOutPerMillion: 10,
136
+ model: "gpt-5.4-nano",
137
+ tokenCostInPerMillion: 0.2,
138
+ tokenCostOutPerMillion: 1.25,
132
139
  },
133
- "openai:gpt-5.2-pro": {
140
+ "openai:gpt-5.5": {
134
141
  provider: "openai",
135
- model: "gpt-5.2-pro",
136
- tokenCostInPerMillion: 15,
137
- tokenCostOutPerMillion: 60,
142
+ model: "gpt-5.5",
143
+ tokenCostInPerMillion: 5,
144
+ tokenCostOutPerMillion: 30,
138
145
  },
139
- "openai:o3": {
146
+ "openai:gpt-5.5-pro": {
140
147
  provider: "openai",
141
- model: "o3",
142
- tokenCostInPerMillion: 10,
143
- tokenCostOutPerMillion: 40,
148
+ model: "gpt-5.5-pro",
149
+ tokenCostInPerMillion: 30,
150
+ tokenCostOutPerMillion: 180,
144
151
  },
145
- "openai:o3-mini": {
152
+ "openai:o3": {
146
153
  provider: "openai",
147
- model: "o3-mini",
148
- tokenCostInPerMillion: 1.1,
149
- tokenCostOutPerMillion: 4.4,
154
+ model: "o3",
155
+ tokenCostInPerMillion: 2,
156
+ tokenCostOutPerMillion: 8,
150
157
  },
151
158
  "openai:o4-mini": {
152
159
  provider: "openai",
@@ -185,13 +192,13 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
185
192
  tokenCostInPerMillion: 3,
186
193
  tokenCostOutPerMillion: 15,
187
194
  },
188
- "anthropic:haiku-4-6": {
195
+ "anthropic:opus-4-7": {
189
196
  provider: "anthropic",
190
- model: "claude-haiku-4-6",
191
- tokenCostInPerMillion: 1,
192
- tokenCostOutPerMillion: 5,
197
+ model: "claude-opus-4-7",
198
+ tokenCostInPerMillion: 5,
199
+ tokenCostOutPerMillion: 25,
193
200
  },
194
- // Gemini
201
+ // Gemini — base (≤200k context) pricing for tiered models
195
202
  "gemini:flash-2.5": {
196
203
  provider: "gemini",
197
204
  model: "gemini-2.5-flash",
@@ -210,31 +217,49 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
210
217
  tokenCostInPerMillion: 1.25,
211
218
  tokenCostOutPerMillion: 10,
212
219
  },
220
+ "gemini:pro-3.1-preview": {
221
+ provider: "gemini",
222
+ model: "gemini-3.1-pro-preview",
223
+ tokenCostInPerMillion: 2,
224
+ tokenCostOutPerMillion: 12,
225
+ },
226
+ "gemini:flash-3-preview": {
227
+ provider: "gemini",
228
+ model: "gemini-3-flash-preview",
229
+ tokenCostInPerMillion: 0.5,
230
+ tokenCostOutPerMillion: 3,
231
+ },
232
+ "gemini:flash-3.1-lite-preview": {
233
+ provider: "gemini",
234
+ model: "gemini-3.1-flash-lite-preview",
235
+ tokenCostInPerMillion: 0.25,
236
+ tokenCostOutPerMillion: 1.5,
237
+ },
213
238
  // DeepSeek — cache miss prices
214
- "deepseek:deepseek-chat": {
239
+ "deepseek:v4-flash": {
215
240
  provider: "deepseek",
216
- model: "deepseek-chat",
217
- tokenCostInPerMillion: 0.28,
218
- tokenCostOutPerMillion: 0.42,
241
+ model: "deepseek-v4-flash",
242
+ tokenCostInPerMillion: 0.14,
243
+ tokenCostOutPerMillion: 0.28,
219
244
  },
220
- "deepseek:deepseek-reasoner": {
245
+ "deepseek:v4-pro": {
221
246
  provider: "deepseek",
222
- model: "deepseek-reasoner",
223
- tokenCostInPerMillion: 0.28,
224
- tokenCostOutPerMillion: 0.42,
247
+ model: "deepseek-v4-pro",
248
+ tokenCostInPerMillion: 1.74,
249
+ tokenCostOutPerMillion: 3.48,
225
250
  },
226
- // Moonshot / Kimi
251
+ // Moonshot / Kimi — cache miss prices
227
252
  "moonshot:kimi-k2.5": {
228
253
  provider: "moonshot",
229
254
  model: "kimi-k2.5",
230
- tokenCostInPerMillion: 0.5,
255
+ tokenCostInPerMillion: 0.6,
231
256
  tokenCostOutPerMillion: 2.5,
232
257
  },
233
- "moonshot:kimi-k1.5": {
258
+ "moonshot:kimi-k2.6": {
234
259
  provider: "moonshot",
235
- model: "kimi-k1.5",
236
- tokenCostInPerMillion: 0.5,
237
- tokenCostOutPerMillion: 2.5,
260
+ model: "kimi-k2.6",
261
+ tokenCostInPerMillion: 0.95,
262
+ tokenCostOutPerMillion: 4,
238
263
  },
239
264
  // Claude Code — subscription-based, zero token cost
240
265
  "claude-code:sonnet": {
@@ -256,17 +281,23 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
256
281
  tokenCostOutPerMillion: 0,
257
282
  },
258
283
  // Z.ai
284
+ "zai:glm-5-1": {
285
+ provider: "zai",
286
+ model: "glm-5.1",
287
+ tokenCostInPerMillion: 1.4,
288
+ tokenCostOutPerMillion: 4.4,
289
+ },
259
290
  "zai:glm-5": {
260
291
  provider: "zai",
261
292
  model: "glm-5",
262
293
  tokenCostInPerMillion: 1,
263
294
  tokenCostOutPerMillion: 3.2,
264
295
  },
265
- "zai:glm-5-code": {
296
+ "zai:glm-5-turbo": {
266
297
  provider: "zai",
267
- model: "glm-5-code",
298
+ model: "glm-5-turbo",
268
299
  tokenCostInPerMillion: 1.2,
269
- tokenCostOutPerMillion: 5,
300
+ tokenCostOutPerMillion: 4,
270
301
  },
271
302
  "zai:glm-4-7": {
272
303
  provider: "zai",
@@ -292,6 +323,12 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
292
323
  tokenCostInPerMillion: 0.6,
293
324
  tokenCostOutPerMillion: 2.2,
294
325
  },
326
+ "zai:glm-4-5-x": {
327
+ provider: "zai",
328
+ model: "glm-4.5-x",
329
+ tokenCostInPerMillion: 2.2,
330
+ tokenCostOutPerMillion: 8.9,
331
+ },
295
332
  "zai:glm-4-5-air": {
296
333
  provider: "zai",
297
334
  model: "glm-4.5-air",
@@ -304,30 +341,42 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
304
341
  tokenCostInPerMillion: 1.1,
305
342
  tokenCostOutPerMillion: 4.5,
306
343
  },
307
- // Alibaba (Qwen via DashScope)
344
+ // Alibaba (Qwen via DashScope, international/Singapore deployment, base tier)
308
345
  "alibaba:qwen3-max": {
309
346
  provider: "alibaba",
310
347
  model: "qwen3-max",
311
- tokenCostInPerMillion: 0.359,
312
- tokenCostOutPerMillion: 1.434,
348
+ tokenCostInPerMillion: 1.2,
349
+ tokenCostOutPerMillion: 6,
350
+ },
351
+ "alibaba:qwen3.6-plus": {
352
+ provider: "alibaba",
353
+ model: "qwen3.6-plus",
354
+ tokenCostInPerMillion: 0.276,
355
+ tokenCostOutPerMillion: 1.651,
313
356
  },
314
357
  "alibaba:qwen3.5-plus": {
315
358
  provider: "alibaba",
316
359
  model: "qwen3.5-plus",
317
- tokenCostInPerMillion: 0.115,
318
- tokenCostOutPerMillion: 0.688,
360
+ tokenCostInPerMillion: 0.4,
361
+ tokenCostOutPerMillion: 2.4,
362
+ },
363
+ "alibaba:qwen3.5-flash": {
364
+ provider: "alibaba",
365
+ model: "qwen3.5-flash",
366
+ tokenCostInPerMillion: 0.1,
367
+ tokenCostOutPerMillion: 0.4,
319
368
  },
320
369
  "alibaba:qwen-plus": {
321
370
  provider: "alibaba",
322
371
  model: "qwen-plus",
323
- tokenCostInPerMillion: 0.115,
324
- tokenCostOutPerMillion: 0.287,
372
+ tokenCostInPerMillion: 0.4,
373
+ tokenCostOutPerMillion: 1.2,
325
374
  },
326
375
  "alibaba:qwen-flash": {
327
376
  provider: "alibaba",
328
377
  model: "qwen-flash",
329
- tokenCostInPerMillion: 0.022,
330
- tokenCostOutPerMillion: 0.216,
378
+ tokenCostInPerMillion: 0.05,
379
+ tokenCostOutPerMillion: 0.4,
331
380
  },
332
381
  "alibaba:qwq-plus": {
333
382
  provider: "alibaba",
@@ -338,14 +387,14 @@ const MODEL_CONFIG_RAW: Record<ModelAliasKey, ModelConfigEntry> = {
338
387
  "alibaba:qwen3-coder-plus": {
339
388
  provider: "alibaba",
340
389
  model: "qwen3-coder-plus",
341
- tokenCostInPerMillion: 0.574,
342
- tokenCostOutPerMillion: 2.294,
390
+ tokenCostInPerMillion: 1,
391
+ tokenCostOutPerMillion: 5,
343
392
  },
344
393
  "alibaba:qwen3-coder-flash": {
345
394
  provider: "alibaba",
346
395
  model: "qwen3-coder-flash",
347
- tokenCostInPerMillion: 0.144,
348
- tokenCostOutPerMillion: 0.574,
396
+ tokenCostInPerMillion: 0.3,
397
+ tokenCostOutPerMillion: 1.5,
349
398
  },
350
399
  };
351
400
 
@@ -397,13 +446,13 @@ export function getModelConfig(alias: string): ModelConfigEntry | null {
397
446
 
398
447
  export const DEFAULT_MODEL_BY_PROVIDER: Readonly<Record<ProviderName, ModelAliasKey>> =
399
448
  Object.freeze({
400
- openai: "openai:gpt-5.2",
449
+ openai: "openai:gpt-5.4",
401
450
  anthropic: "anthropic:sonnet-4-6",
402
451
  gemini: "gemini:flash-2.5",
403
- deepseek: "deepseek:deepseek-chat",
404
- moonshot: "moonshot:kimi-k2.5",
452
+ deepseek: "deepseek:v4-flash",
453
+ moonshot: "moonshot:kimi-k2.6",
405
454
  "claude-code": "claude-code:sonnet",
406
- zai: "zai:glm-5",
455
+ zai: "zai:glm-5-1",
407
456
  alibaba: "alibaba:qwen3-max",
408
457
  } as const);
409
458
 
@@ -0,0 +1,78 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { decideTransition } from "../lifecycle-policy";
3
+
4
+ describe("decideTransition", () => {
5
+ test("start with pending task and ready dependencies returns ok", () => {
6
+ const result = decideTransition({
7
+ op: "start",
8
+ taskState: "pending",
9
+ dependenciesReady: true,
10
+ });
11
+ expect(result).toEqual({ ok: true });
12
+ });
13
+
14
+ test("start with pending task and unready dependencies is blocked by dependencies", () => {
15
+ const result = decideTransition({
16
+ op: "start",
17
+ taskState: "pending",
18
+ dependenciesReady: false,
19
+ });
20
+ expect(result).toEqual({
21
+ ok: false,
22
+ code: "unsupported_lifecycle",
23
+ reason: "dependencies",
24
+ });
25
+ });
26
+
27
+ test("restart from done state returns ok regardless of dependencies", () => {
28
+ const result = decideTransition({
29
+ op: "restart",
30
+ taskState: "done",
31
+ dependenciesReady: false,
32
+ });
33
+ expect(result).toEqual({ ok: true });
34
+ });
35
+
36
+ test("restart from running state is blocked by policy", () => {
37
+ const result = decideTransition({
38
+ op: "restart",
39
+ taskState: "running",
40
+ dependenciesReady: true,
41
+ });
42
+ expect(result).toEqual({
43
+ ok: false,
44
+ code: "unsupported_lifecycle",
45
+ reason: "policy",
46
+ });
47
+ });
48
+
49
+ test("throws for invalid op", () => {
50
+ expect(() =>
51
+ decideTransition({
52
+ op: "stop" as unknown as "start",
53
+ taskState: "pending",
54
+ dependenciesReady: true,
55
+ })
56
+ ).toThrow(/Invalid op/);
57
+ });
58
+
59
+ test("throws for non-string taskState", () => {
60
+ expect(() =>
61
+ decideTransition({
62
+ op: "start",
63
+ taskState: 42 as unknown as string,
64
+ dependenciesReady: true,
65
+ })
66
+ ).toThrow(/Invalid taskState/);
67
+ });
68
+
69
+ test("throws for non-boolean dependenciesReady", () => {
70
+ expect(() =>
71
+ decideTransition({
72
+ op: "start",
73
+ taskState: "pending",
74
+ dependenciesReady: "yes" as unknown as boolean,
75
+ })
76
+ ).toThrow(/Invalid dependenciesReady/);
77
+ });
78
+ });