@acpus/runtime 0.8.0 → 0.9.0-alpha.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 (289) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/admission/input.d.ts +5 -0
  3. package/dist/admission/input.d.ts.map +1 -0
  4. package/dist/admission/input.js +17 -0
  5. package/dist/admission/input.js.map +1 -0
  6. package/dist/artifacts/rewrite.d.ts +7 -0
  7. package/dist/artifacts/rewrite.d.ts.map +1 -0
  8. package/dist/artifacts/rewrite.js +28 -0
  9. package/dist/artifacts/rewrite.js.map +1 -0
  10. package/dist/control/agent-overrides.d.ts +4 -0
  11. package/dist/control/agent-overrides.d.ts.map +1 -0
  12. package/dist/control/agent-overrides.js +40 -0
  13. package/dist/control/agent-overrides.js.map +1 -0
  14. package/dist/daemon/loop.d.ts +16 -0
  15. package/dist/daemon/loop.d.ts.map +1 -0
  16. package/dist/daemon/loop.js +191 -0
  17. package/dist/daemon/loop.js.map +1 -0
  18. package/dist/daemon/sessions.d.ts +23 -0
  19. package/dist/daemon/sessions.d.ts.map +1 -0
  20. package/dist/daemon/sessions.js +190 -0
  21. package/dist/daemon/sessions.js.map +1 -0
  22. package/dist/daemon/socket.d.ts +82 -0
  23. package/dist/daemon/socket.d.ts.map +1 -0
  24. package/dist/daemon/socket.js +341 -0
  25. package/dist/daemon/socket.js.map +1 -0
  26. package/dist/daemon/tick.d.ts +9 -0
  27. package/dist/daemon/tick.d.ts.map +1 -0
  28. package/dist/daemon/tick.js +18 -0
  29. package/dist/daemon/tick.js.map +1 -0
  30. package/dist/evaluation/admissible.d.ts +2 -0
  31. package/dist/evaluation/admissible.d.ts.map +1 -0
  32. package/dist/evaluation/admissible.js +50 -0
  33. package/dist/evaluation/admissible.js.map +1 -0
  34. package/dist/evaluation/evaluator.d.ts +14 -0
  35. package/dist/evaluation/evaluator.d.ts.map +1 -0
  36. package/dist/evaluation/evaluator.js +34 -0
  37. package/dist/evaluation/evaluator.js.map +1 -0
  38. package/dist/evaluation/loop-limit.d.ts +4 -0
  39. package/dist/evaluation/loop-limit.d.ts.map +1 -0
  40. package/dist/evaluation/loop-limit.js +9 -0
  41. package/dist/evaluation/loop-limit.js.map +1 -0
  42. package/dist/evaluation/schema.d.ts +4 -0
  43. package/dist/evaluation/schema.d.ts.map +1 -0
  44. package/dist/evaluation/schema.js +92 -0
  45. package/dist/evaluation/schema.js.map +1 -0
  46. package/dist/execution/advance.d.ts +18 -0
  47. package/dist/execution/advance.d.ts.map +1 -0
  48. package/dist/execution/advance.js +57 -0
  49. package/dist/execution/advance.js.map +1 -0
  50. package/dist/execution/agent-node.d.ts +23 -0
  51. package/dist/execution/agent-node.d.ts.map +1 -0
  52. package/dist/execution/agent-node.js +512 -0
  53. package/dist/execution/agent-node.js.map +1 -0
  54. package/dist/execution/duration.d.ts +2 -0
  55. package/dist/execution/duration.d.ts.map +1 -0
  56. package/dist/execution/duration.js +15 -0
  57. package/dist/execution/duration.js.map +1 -0
  58. package/dist/execution/ir.d.ts +3 -0
  59. package/dist/execution/ir.d.ts.map +1 -0
  60. package/dist/execution/ir.js +46 -0
  61. package/dist/execution/ir.js.map +1 -0
  62. package/dist/execution/scheduler.d.ts +59 -0
  63. package/dist/execution/scheduler.d.ts.map +1 -0
  64. package/dist/execution/scheduler.js +306 -0
  65. package/dist/execution/scheduler.js.map +1 -0
  66. package/dist/execution/task-executor.d.ts +14 -0
  67. package/dist/execution/task-executor.d.ts.map +1 -0
  68. package/dist/execution/task-executor.js +186 -0
  69. package/dist/execution/task-executor.js.map +1 -0
  70. package/dist/hooks/config.d.ts +32 -0
  71. package/dist/hooks/config.d.ts.map +1 -0
  72. package/dist/hooks/config.js +112 -0
  73. package/dist/hooks/config.js.map +1 -0
  74. package/dist/hooks/context.d.ts +50 -0
  75. package/dist/hooks/context.d.ts.map +1 -0
  76. package/dist/hooks/context.js +116 -0
  77. package/dist/hooks/context.js.map +1 -0
  78. package/dist/hooks/events.d.ts +22 -0
  79. package/dist/hooks/events.d.ts.map +1 -0
  80. package/dist/hooks/events.js +41 -0
  81. package/dist/hooks/events.js.map +1 -0
  82. package/dist/hooks/journal.d.ts +21 -19
  83. package/dist/hooks/journal.d.ts.map +1 -1
  84. package/dist/hooks/journal.js +1 -48
  85. package/dist/hooks/journal.js.map +1 -1
  86. package/dist/hooks/loader.d.ts +33 -39
  87. package/dist/hooks/loader.d.ts.map +1 -1
  88. package/dist/hooks/loader.js +79 -95
  89. package/dist/hooks/loader.js.map +1 -1
  90. package/dist/hooks/runner.d.ts +14 -41
  91. package/dist/hooks/runner.d.ts.map +1 -1
  92. package/dist/hooks/runner.js +177 -145
  93. package/dist/hooks/runner.js.map +1 -1
  94. package/dist/index.d.ts +13 -28
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +13 -33
  97. package/dist/index.js.map +1 -1
  98. package/dist/runs/advance-runtime.d.ts +50 -0
  99. package/dist/runs/advance-runtime.d.ts.map +1 -0
  100. package/dist/runs/advance-runtime.js +82 -0
  101. package/dist/runs/advance-runtime.js.map +1 -0
  102. package/dist/runs/use-cases.d.ts +111 -0
  103. package/dist/runs/use-cases.d.ts.map +1 -0
  104. package/dist/runs/use-cases.js +539 -0
  105. package/dist/runs/use-cases.js.map +1 -0
  106. package/dist/scheduler/advance.d.ts +65 -0
  107. package/dist/scheduler/advance.d.ts.map +1 -0
  108. package/dist/scheduler/advance.js +435 -0
  109. package/dist/scheduler/advance.js.map +1 -0
  110. package/dist/scheduler/control.d.ts +49 -0
  111. package/dist/scheduler/control.d.ts.map +1 -0
  112. package/dist/scheduler/control.js +212 -0
  113. package/dist/scheduler/control.js.map +1 -0
  114. package/dist/scheduler/event-codec.d.ts +4 -0
  115. package/dist/scheduler/event-codec.d.ts.map +1 -0
  116. package/dist/scheduler/event-codec.js +57 -0
  117. package/dist/scheduler/event-codec.js.map +1 -0
  118. package/dist/scheduler/events.d.ts +193 -0
  119. package/dist/scheduler/events.d.ts.map +1 -0
  120. package/dist/scheduler/events.js +13 -0
  121. package/dist/scheduler/events.js.map +1 -0
  122. package/dist/scheduler/fork-seed.d.ts +38 -0
  123. package/dist/scheduler/fork-seed.d.ts.map +1 -0
  124. package/dist/scheduler/fork-seed.js +590 -0
  125. package/dist/scheduler/fork-seed.js.map +1 -0
  126. package/dist/scheduler/identity.d.ts +8 -0
  127. package/dist/scheduler/identity.d.ts.map +1 -0
  128. package/dist/scheduler/identity.js +49 -0
  129. package/dist/scheduler/identity.js.map +1 -0
  130. package/dist/scheduler/ir-walk.d.ts +3 -0
  131. package/dist/scheduler/ir-walk.d.ts.map +1 -0
  132. package/dist/scheduler/ir-walk.js +25 -0
  133. package/dist/scheduler/ir-walk.js.map +1 -0
  134. package/dist/scheduler/limiter.d.ts +7 -0
  135. package/dist/scheduler/limiter.d.ts.map +1 -0
  136. package/dist/scheduler/limiter.js +10 -0
  137. package/dist/scheduler/limiter.js.map +1 -0
  138. package/dist/scheduler/materialize.d.ts +7 -0
  139. package/dist/scheduler/materialize.d.ts.map +1 -0
  140. package/dist/scheduler/materialize.js +756 -0
  141. package/dist/scheduler/materialize.js.map +1 -0
  142. package/dist/scheduler/membership.d.ts +12 -0
  143. package/dist/scheduler/membership.d.ts.map +1 -0
  144. package/dist/scheduler/membership.js +74 -0
  145. package/dist/scheduler/membership.js.map +1 -0
  146. package/dist/scheduler/node-executor.d.ts +15 -0
  147. package/dist/scheduler/node-executor.d.ts.map +1 -0
  148. package/dist/scheduler/node-executor.js +69 -0
  149. package/dist/scheduler/node-executor.js.map +1 -0
  150. package/dist/scheduler/runtime-runner.d.ts +44 -0
  151. package/dist/scheduler/runtime-runner.d.ts.map +1 -0
  152. package/dist/scheduler/runtime-runner.js +155 -0
  153. package/dist/scheduler/runtime-runner.js.map +1 -0
  154. package/dist/scheduler/scope.d.ts +6 -0
  155. package/dist/scheduler/scope.d.ts.map +1 -0
  156. package/dist/scheduler/scope.js +56 -0
  157. package/dist/scheduler/scope.js.map +1 -0
  158. package/dist/scheduler/store-port.d.ts +211 -0
  159. package/dist/scheduler/store-port.d.ts.map +1 -0
  160. package/dist/scheduler/store-port.js +28 -0
  161. package/dist/scheduler/store-port.js.map +1 -0
  162. package/dist/scheduler/transitions.d.ts +89 -0
  163. package/dist/scheduler/transitions.d.ts.map +1 -0
  164. package/dist/scheduler/transitions.js +967 -0
  165. package/dist/scheduler/transitions.js.map +1 -0
  166. package/dist/scheduler/types.d.ts +146 -0
  167. package/dist/scheduler/types.d.ts.map +1 -0
  168. package/dist/{executors → scheduler}/types.js.map +1 -1
  169. package/dist/store/store.d.ts +390 -0
  170. package/dist/store/store.d.ts.map +1 -0
  171. package/dist/store/store.js +3053 -0
  172. package/dist/store/store.js.map +1 -0
  173. package/dist/visualization/overlay.d.ts +86 -0
  174. package/dist/visualization/overlay.d.ts.map +1 -0
  175. package/dist/visualization/overlay.js +161 -0
  176. package/dist/visualization/overlay.js.map +1 -0
  177. package/package.json +19 -37
  178. package/README.md +0 -7
  179. package/dist/agent-telemetry.d.ts +0 -56
  180. package/dist/agent-telemetry.d.ts.map +0 -1
  181. package/dist/agent-telemetry.js +0 -304
  182. package/dist/agent-telemetry.js.map +0 -1
  183. package/dist/artifacts.d.ts +0 -45
  184. package/dist/artifacts.d.ts.map +0 -1
  185. package/dist/artifacts.js +0 -163
  186. package/dist/artifacts.js.map +0 -1
  187. package/dist/attempt-artifacts.d.ts +0 -35
  188. package/dist/attempt-artifacts.d.ts.map +0 -1
  189. package/dist/attempt-artifacts.js +0 -90
  190. package/dist/attempt-artifacts.js.map +0 -1
  191. package/dist/client.d.ts +0 -82
  192. package/dist/client.d.ts.map +0 -1
  193. package/dist/client.js +0 -200
  194. package/dist/client.js.map +0 -1
  195. package/dist/evaluator.d.ts +0 -36
  196. package/dist/evaluator.d.ts.map +0 -1
  197. package/dist/evaluator.js +0 -101
  198. package/dist/evaluator.js.map +0 -1
  199. package/dist/executors/agent.d.ts +0 -103
  200. package/dist/executors/agent.d.ts.map +0 -1
  201. package/dist/executors/agent.js +0 -516
  202. package/dist/executors/agent.js.map +0 -1
  203. package/dist/executors/mock-program.d.ts +0 -26
  204. package/dist/executors/mock-program.d.ts.map +0 -1
  205. package/dist/executors/mock-program.js +0 -100
  206. package/dist/executors/mock-program.js.map +0 -1
  207. package/dist/executors/output-preview.d.ts +0 -2
  208. package/dist/executors/output-preview.d.ts.map +0 -1
  209. package/dist/executors/output-preview.js +0 -20
  210. package/dist/executors/output-preview.js.map +0 -1
  211. package/dist/executors/program.d.ts +0 -30
  212. package/dist/executors/program.d.ts.map +0 -1
  213. package/dist/executors/program.js +0 -230
  214. package/dist/executors/program.js.map +0 -1
  215. package/dist/executors/types.d.ts +0 -32
  216. package/dist/executors/types.d.ts.map +0 -1
  217. package/dist/fork.d.ts +0 -74
  218. package/dist/fork.d.ts.map +0 -1
  219. package/dist/fork.js +0 -278
  220. package/dist/fork.js.map +0 -1
  221. package/dist/hooks/payload.d.ts +0 -17
  222. package/dist/hooks/payload.d.ts.map +0 -1
  223. package/dist/hooks/payload.js +0 -45
  224. package/dist/hooks/payload.js.map +0 -1
  225. package/dist/interpreter.d.ts +0 -168
  226. package/dist/interpreter.d.ts.map +0 -1
  227. package/dist/interpreter.js +0 -1747
  228. package/dist/interpreter.js.map +0 -1
  229. package/dist/keys.d.ts +0 -77
  230. package/dist/keys.d.ts.map +0 -1
  231. package/dist/keys.js +0 -382
  232. package/dist/keys.js.map +0 -1
  233. package/dist/run-control.d.ts +0 -45
  234. package/dist/run-control.d.ts.map +0 -1
  235. package/dist/run-control.js +0 -255
  236. package/dist/run-control.js.map +0 -1
  237. package/dist/run-id.d.ts +0 -3
  238. package/dist/run-id.d.ts.map +0 -1
  239. package/dist/run-id.js +0 -10
  240. package/dist/run-id.js.map +0 -1
  241. package/dist/state-machine.d.ts +0 -31
  242. package/dist/state-machine.d.ts.map +0 -1
  243. package/dist/state-machine.js +0 -102
  244. package/dist/state-machine.js.map +0 -1
  245. package/dist/store.d.ts +0 -92
  246. package/dist/store.d.ts.map +0 -1
  247. package/dist/store.js +0 -420
  248. package/dist/store.js.map +0 -1
  249. package/dist/supervisor-app.d.ts +0 -27
  250. package/dist/supervisor-app.d.ts.map +0 -1
  251. package/dist/supervisor-app.js +0 -694
  252. package/dist/supervisor-app.js.map +0 -1
  253. package/dist/supervisor-discovery.d.ts +0 -18
  254. package/dist/supervisor-discovery.d.ts.map +0 -1
  255. package/dist/supervisor-discovery.js +0 -180
  256. package/dist/supervisor-discovery.js.map +0 -1
  257. package/dist/supervisor-entry.d.ts +0 -11
  258. package/dist/supervisor-entry.d.ts.map +0 -1
  259. package/dist/supervisor-entry.js +0 -50
  260. package/dist/supervisor-entry.js.map +0 -1
  261. package/dist/supervisor-lock.d.ts +0 -26
  262. package/dist/supervisor-lock.d.ts.map +0 -1
  263. package/dist/supervisor-lock.js +0 -49
  264. package/dist/supervisor-lock.js.map +0 -1
  265. package/dist/supervisor-runner.d.ts +0 -11
  266. package/dist/supervisor-runner.d.ts.map +0 -1
  267. package/dist/supervisor-runner.js +0 -128
  268. package/dist/supervisor-runner.js.map +0 -1
  269. package/dist/types.d.ts +0 -375
  270. package/dist/types.d.ts.map +0 -1
  271. package/dist/types.js +0 -7
  272. package/dist/types.js.map +0 -1
  273. package/dist/validate-input.d.ts +0 -27
  274. package/dist/validate-input.d.ts.map +0 -1
  275. package/dist/validate-input.js +0 -56
  276. package/dist/validate-input.js.map +0 -1
  277. package/dist/validate-signal.d.ts +0 -14
  278. package/dist/validate-signal.d.ts.map +0 -1
  279. package/dist/validate-signal.js +0 -31
  280. package/dist/validate-signal.js.map +0 -1
  281. package/dist/workflow-context.d.ts +0 -4
  282. package/dist/workflow-context.d.ts.map +0 -1
  283. package/dist/workflow-context.js +0 -11
  284. package/dist/workflow-context.js.map +0 -1
  285. package/dist/workflow-outputs.d.ts +0 -9
  286. package/dist/workflow-outputs.d.ts.map +0 -1
  287. package/dist/workflow-outputs.js +0 -60
  288. package/dist/workflow-outputs.js.map +0 -1
  289. /package/dist/{executors → scheduler}/types.js +0 -0
@@ -0,0 +1,967 @@
1
+ import { isTerminalAttemptStatus, isTerminalFrameStatus, isTerminalGroupMemberStatus, isTerminalInstanceStatus } from "./events.js";
2
+ import { ancestorGroupMembersForNode, descendantFramesForFrame, descendantFramesForMember, descendantGroupKeysForFrame, descendantGroupKeysForMember, descendantGroupMembersForFrame, descendantGroupMembersForMember, descendantInstancesForFrame, descendantInstancesForMember } from "./membership.js";
3
+ export function signalTimeoutEvents(projection, now) {
4
+ if (projection.run.status === "paused")
5
+ return [];
6
+ return Object.values(projection.signalWaits).flatMap(wait => {
7
+ if (wait.status !== "awaiting" || wait.deadlineAt === undefined || wait.deadlineAt > now.toISOString())
8
+ return [];
9
+ const instance = projection.instances[wait.nodeKey];
10
+ const error = {
11
+ reason: "signal_timeout",
12
+ ...(wait.timeoutMessage === undefined ? {} : { message: wait.timeoutMessage }),
13
+ };
14
+ const members = ancestorGroupMembersForNode(projection, wait.nodeKey).filter(member => member.status === "running");
15
+ return [
16
+ { type: "signal.timed_out", payload: { nodeKey: wait.nodeKey, terminalReason: "signal_timeout", ...(wait.timeoutMessage === undefined ? {} : { message: wait.timeoutMessage }) } },
17
+ ...(instance && instance.status === "awaiting"
18
+ ? [{ type: "instance.failed", payload: { nodeKey: wait.nodeKey, error, statusReason: "signal_timeout" } }]
19
+ : []),
20
+ ...members.map(member => ({ type: "group.member_failed", payload: { memberKey: member.memberKey, error, terminalReason: "signal_timeout" } })),
21
+ ];
22
+ });
23
+ }
24
+ export function attemptTimeoutEvents(projection, now) {
25
+ return Object.values(projection.attempts).flatMap(attempt => {
26
+ if (attempt.status !== "started" || attempt.deadlineAt === undefined || attempt.deadlineAt > now.toISOString())
27
+ return [];
28
+ const instance = projection.instances[attempt.nodeKey];
29
+ const members = ancestorGroupMembersForNode(projection, attempt.nodeKey).filter(member => member.status === "running");
30
+ const error = { reason: "attempt_timeout" };
31
+ return [
32
+ { type: "attempt.timed_out", payload: { attemptId: attempt.attemptId, error } },
33
+ ...(instance && (instance.status === "running" || instance.status === "awaiting")
34
+ ? [{ type: "instance.failed", payload: { nodeKey: instance.nodeKey, error, statusReason: "timed_out" } }]
35
+ : []),
36
+ ...members.map(member => ({ type: "group.member_failed", payload: { memberKey: member.memberKey, error, terminalReason: "timed_out" } })),
37
+ ];
38
+ });
39
+ }
40
+ export function createSchedulerProjection(runId) {
41
+ return {
42
+ run: { runId, status: "pending", paused: false },
43
+ frames: {},
44
+ instances: {},
45
+ attempts: {},
46
+ groups: {},
47
+ groupMembers: {},
48
+ signalWaits: {},
49
+ branchDecisions: {},
50
+ };
51
+ }
52
+ export function applySchedulerEvent(projection, event) {
53
+ const next = cloneProjection(projection);
54
+ applyMutable(next, event);
55
+ return next;
56
+ }
57
+ export function applySchedulerEvents(projection, events) {
58
+ return events.reduce((current, event) => applySchedulerEvent(current, event), projection);
59
+ }
60
+ export function applyTimestampedSchedulerEvents(runId, events) {
61
+ let projection = createSchedulerProjection(runId);
62
+ const timings = {
63
+ frame: new Map(),
64
+ instance: new Map(),
65
+ attempt: new Map(),
66
+ member: new Map(),
67
+ signal: new Map(),
68
+ };
69
+ for (const item of events) {
70
+ const next = applySchedulerEvent(projection, item.event);
71
+ syncTimingMap(timings.frame, projection.frames, next.frames, item.createdAt, frame => frame.status);
72
+ syncTimingMap(timings.instance, projection.instances, next.instances, item.createdAt, instance => instance.status);
73
+ syncTimingMap(timings.attempt, projection.attempts, next.attempts, item.createdAt, attempt => attempt.status);
74
+ syncTimingMap(timings.member, projection.groupMembers, next.groupMembers, item.createdAt, member => member.status);
75
+ syncTimingMap(timings.signal, projection.signalWaits, next.signalWaits, item.createdAt, wait => wait.status);
76
+ projection = next;
77
+ }
78
+ return { projection, timings };
79
+ }
80
+ export function evaluateGroupCompletion(group, members) {
81
+ if (group.status === "completed")
82
+ return { status: "completed", acceptedMemberKeys: [], cancelRemaining: false };
83
+ if (group.status !== "running")
84
+ return { status: "failed", reason: group.error?.message ?? "group_not_running", cancelRemaining: false };
85
+ if (group.strategy === "all") {
86
+ const failed = members.find(member => member.status === "failed");
87
+ if (failed)
88
+ return { status: "failed", reason: failed.terminalReason ?? "member_failed", cancelRemaining: true };
89
+ return members.every(member => member.status === "completed")
90
+ ? { status: "completed", acceptedMemberKeys: members.map(member => member.memberKey), cancelRemaining: false }
91
+ : { status: "running" };
92
+ }
93
+ if (group.strategy === "race") {
94
+ const winner = members
95
+ .filter(member => member.status === "completed")
96
+ .sort(byCompletionSequence)[0];
97
+ if (winner)
98
+ return { status: "completed", acceptedMemberKeys: [winner.memberKey], cancelRemaining: true };
99
+ return members.every(member => member.status === "failed" || member.status === "cancelled")
100
+ ? { status: "failed", reason: "race_no_success", cancelRemaining: false }
101
+ : { status: "running" };
102
+ }
103
+ const quorum = requirePositiveQuorum(group);
104
+ const completed = members
105
+ .filter(member => member.status === "completed")
106
+ .sort(byCompletionSequence);
107
+ if (completed.length >= quorum) {
108
+ return {
109
+ status: "completed",
110
+ acceptedMemberKeys: completed.slice(0, quorum).map(member => member.memberKey),
111
+ cancelRemaining: true,
112
+ };
113
+ }
114
+ const possible = completed.length + members.filter(member => member.status === "ready" || member.status === "running").length;
115
+ return possible < quorum
116
+ ? { status: "failed", reason: "quorum_impossible", cancelRemaining: true }
117
+ : { status: "running" };
118
+ }
119
+ export function groupCompletionEvents(projection, groupKey) {
120
+ const group = requireKey(projection.groups, groupKey, "group");
121
+ if (group.status !== "running")
122
+ return [];
123
+ const members = Object.values(projection.groupMembers).filter(member => member.groupKey === groupKey);
124
+ const completion = evaluateGroupCompletion(group, members);
125
+ if (completion.status === "running")
126
+ return [];
127
+ if (completion.status === "completed") {
128
+ const accepted = new Set(completion.acceptedMemberKeys);
129
+ return [
130
+ ...completion.cancelRemaining ? members
131
+ .filter(member => !accepted.has(member.memberKey) && (member.status === "ready" || member.status === "running"))
132
+ .flatMap(member => cancellationEventsForMember(projection, member, group.strategy === "race" ? "race_lost" : "quorum_reached")) : [],
133
+ { type: "group.completed", payload: { groupKey, result: { acceptedMemberKeys: completion.acceptedMemberKeys } } },
134
+ ];
135
+ }
136
+ return [
137
+ ...completion.cancelRemaining ? members
138
+ .filter(member => member.status === "ready" || member.status === "running")
139
+ .flatMap(member => cancellationEventsForMember(projection, member, "parent_failed")) : [],
140
+ { type: "group.failed", payload: { groupKey, error: { reason: completion.reason } } },
141
+ ];
142
+ }
143
+ export function cancellationEventsForFrame(projection, frameKey, cancelReason) {
144
+ if (!projection.frames[frameKey])
145
+ return [];
146
+ const frames = descendantFramesForFrame(projection, frameKey);
147
+ const frameKeys = new Set(frames.map(frame => frame.frameKey));
148
+ const instances = Object.values(projection.instances)
149
+ .filter(instance => instance.parentFrameKey !== undefined && frameKeys.has(instance.parentFrameKey));
150
+ return cancellationEventsForSubtree(projection, frames, instances, cancelReason);
151
+ }
152
+ export function cancellationEventsForNode(projection, nodeKey, cancelReason) {
153
+ const instance = projection.instances[nodeKey];
154
+ if (!instance)
155
+ return [];
156
+ const member = ancestorGroupMembersForNode(projection, nodeKey)[0];
157
+ return member ? cancellationEventsForMember(projection, member, cancelReason) : cancellationEventsForSubtree(projection, [], [instance], cancelReason);
158
+ }
159
+ function cancellationEventsForSubtree(projection, frames, instances, cancelReason) {
160
+ const frameKeys = new Set(frames.map(frame => frame.frameKey));
161
+ const nodeKeys = new Set(instances.map(instance => instance.nodeKey));
162
+ const groupMembers = Object.values(projection.groupMembers)
163
+ .filter(member => (member.childFrameKey !== undefined && frameKeys.has(member.childFrameKey)) || frameKeys.has(member.memberKey));
164
+ const groupKeys = new Set(Object.values(projection.groups)
165
+ .filter(group => frameKeys.has(group.nodeKey))
166
+ .map(group => group.groupKey));
167
+ const attempts = Object.values(projection.attempts)
168
+ .filter(attempt => attempt.status === "started" && nodeKeys.has(attempt.nodeKey));
169
+ const signalWaits = Object.values(projection.signalWaits)
170
+ .filter(wait => wait.status === "awaiting" && nodeKeys.has(wait.nodeKey));
171
+ return [
172
+ ...attempts.map(attempt => ({ type: "attempt.cancelled", payload: { attemptId: attempt.attemptId, cancelReason } })),
173
+ ...signalWaits.map(wait => ({ type: "signal.cancelled", payload: { nodeKey: wait.nodeKey, cancelReason } })),
174
+ ...instances
175
+ .filter(instance => instance.status === "ready" || instance.status === "running" || instance.status === "awaiting")
176
+ .map(instance => ({ type: "instance.cancelled", payload: { nodeKey: instance.nodeKey, cancelReason } })),
177
+ ...groupMembers
178
+ .filter(member => member.status === "ready" || member.status === "running")
179
+ .map(member => ({ type: "group.member_cancelled", payload: { memberKey: member.memberKey, cancelReason } })),
180
+ ...Object.values(projection.groups)
181
+ .filter(group => groupKeys.has(group.groupKey) && group.status === "running")
182
+ .map(group => ({ type: "group.cancelled", payload: { groupKey: group.groupKey, cancelReason } })),
183
+ ...frames
184
+ .filter(frame => frame.status === "ready" || frame.status === "running" || frame.status === "awaiting")
185
+ .map(frame => ({ type: "frame.cancelled", payload: { frameKey: frame.frameKey, cancelReason } })),
186
+ ];
187
+ }
188
+ function cancellationEventsForMember(projection, member, cancelReason) {
189
+ const instances = memberInstances(projection, member);
190
+ const frames = memberFrames(projection, member);
191
+ const childMembers = descendantGroupMembersForMember(projection, member);
192
+ const childGroupKeys = descendantGroupKeysForMember(projection, member);
193
+ const attempts = instances.flatMap(instance => Object.values(projection.attempts).filter(attempt => attempt.nodeKey === instance.nodeKey && attempt.status === "started"));
194
+ const signalWaits = instances.flatMap(instance => {
195
+ const wait = projection.signalWaits[instance.nodeKey];
196
+ return wait?.status === "awaiting" ? [wait] : [];
197
+ });
198
+ return [
199
+ { type: "group.member_cancelled", payload: { memberKey: member.memberKey, cancelReason } },
200
+ ...childMembers
201
+ .filter(child => child.status === "ready" || child.status === "running")
202
+ .map(child => ({ type: "group.member_cancelled", payload: { memberKey: child.memberKey, cancelReason } })),
203
+ ...childGroupKeys
204
+ .filter(groupKey => projection.groups[groupKey]?.status === "running")
205
+ .map(groupKey => ({ type: "group.cancelled", payload: { groupKey, cancelReason } })),
206
+ ...attempts.map(attempt => ({ type: "attempt.cancelled", payload: { attemptId: attempt.attemptId, cancelReason } })),
207
+ ...signalWaits.map(wait => ({ type: "signal.cancelled", payload: { nodeKey: wait.nodeKey, cancelReason } })),
208
+ ...instances
209
+ .filter(instance => instance.status === "ready" || instance.status === "running" || instance.status === "awaiting")
210
+ .map(instance => ({ type: "instance.cancelled", payload: { nodeKey: instance.nodeKey, cancelReason } })),
211
+ ...frames
212
+ .filter(frame => !isTerminalFrameStatus(frame.status))
213
+ .sort((left, right) => frameDepth(right) - frameDepth(left))
214
+ .map(frame => ({ type: "frame.cancelled", payload: { frameKey: frame.frameKey, cancelReason } })),
215
+ ];
216
+ }
217
+ function memberInstances(projection, member) {
218
+ return descendantInstancesForMember(projection, member);
219
+ }
220
+ function memberFrames(projection, member) {
221
+ return descendantFramesForMember(projection, member);
222
+ }
223
+ function frameDepth(frame) {
224
+ return frame.instancePath?.length ?? 0;
225
+ }
226
+ export function materializeFanoutItems(input) {
227
+ const seen = new Set();
228
+ return input.items.map((item, itemIndex) => {
229
+ const itemKey = input.keyForItem?.(item, itemIndex) ?? itemIndex;
230
+ const normalizedKey = String(itemKey);
231
+ if (seen.has(normalizedKey))
232
+ throw new Error(`Fanout group '${input.groupKey}' has duplicate item key '${normalizedKey}'.`);
233
+ seen.add(normalizedKey);
234
+ return {
235
+ type: "group.member_ready",
236
+ payload: {
237
+ runId: input.runId,
238
+ groupKey: input.groupKey,
239
+ memberKey: `${input.groupKey}[${normalizedKey}]`,
240
+ memberKind: "fanout_item",
241
+ itemKey,
242
+ itemIndex,
243
+ readinessSequence: input.readinessSequenceStart + itemIndex,
244
+ },
245
+ };
246
+ });
247
+ }
248
+ export function nextLoopStep(input) {
249
+ if (input.stop) {
250
+ if (input.result === undefined)
251
+ throw new Error("Loop stop requires the current iteration result.");
252
+ return { action: "complete", output: input.result, terminalReason: "stopped" };
253
+ }
254
+ const nextIter = input.iter + 1;
255
+ if (nextIter < input.maxIterations) {
256
+ return input.result === undefined
257
+ ? { action: "start_iteration", iter: nextIter }
258
+ : { action: "start_iteration", iter: nextIter, previous: input.result };
259
+ }
260
+ const lastResult = input.result ?? input.previous;
261
+ const exhausted = loopExhaustionResult({
262
+ maxIterations: input.maxIterations,
263
+ ...(input.onExhausted === undefined ? {} : { onExhausted: input.onExhausted }),
264
+ ...(lastResult === undefined ? {} : { lastResult }),
265
+ });
266
+ return exhausted.status === "completed"
267
+ ? { action: "complete", output: exhausted.output, terminalReason: exhausted.terminalReason }
268
+ : { action: "fail", error: exhausted.error, terminalReason: exhausted.terminalReason };
269
+ }
270
+ export function resolveScopedNodeKey(scopes, nodeId) {
271
+ for (let index = scopes.length - 1; index >= 0; index -= 1) {
272
+ const found = scopes[index]?.[nodeId];
273
+ if (found)
274
+ return found;
275
+ }
276
+ throw new Error(`Node '${nodeId}' is not visible in the current execution scope.`);
277
+ }
278
+ export function retryTargetClass(status, failureClass) {
279
+ return status === "failed" && failureClass !== "retryable" ? "retryable" : "not_retryable";
280
+ }
281
+ export function loopExhaustionResult(input) {
282
+ if (input.onExhausted === "returnLast" && input.lastResult !== undefined) {
283
+ return { status: "completed", output: input.lastResult, terminalReason: "exhausted_return_last" };
284
+ }
285
+ return {
286
+ status: "failed",
287
+ terminalReason: "loop_exhausted",
288
+ error: { message: `Loop exhausted after ${input.maxIterations} iterations.` },
289
+ };
290
+ }
291
+ function applyMutable(projection, event) {
292
+ if (event.type === "control.run_retry_requested") {
293
+ if (projection.run.status !== "failed")
294
+ throw new Error(`Cannot retry run from ${projection.run.status}.`);
295
+ const fresh = createSchedulerProjection(projection.run.runId);
296
+ projection.run = fresh.run;
297
+ projection.frames = fresh.frames;
298
+ projection.instances = fresh.instances;
299
+ projection.attempts = fresh.attempts;
300
+ projection.groups = fresh.groups;
301
+ projection.groupMembers = fresh.groupMembers;
302
+ projection.signalWaits = fresh.signalWaits;
303
+ projection.branchDecisions = fresh.branchDecisions;
304
+ return;
305
+ }
306
+ if (event.type === "control.paused") {
307
+ assertRunControllable(projection.run.status, "pause");
308
+ projection.run = { ...projection.run, status: "paused", paused: true };
309
+ return;
310
+ }
311
+ if (event.type === "control.resumed") {
312
+ if (projection.run.status !== "paused")
313
+ throw new Error(`Cannot resume run from ${projection.run.status}.`);
314
+ projection.run = { ...projection.run, status: "pending", paused: false };
315
+ return;
316
+ }
317
+ if (event.type === "frame.started") {
318
+ if (projection.frames[event.payload.frameKey])
319
+ throw new Error(`Frame '${event.payload.frameKey}' already exists.`);
320
+ projection.frames[event.payload.frameKey] = compactFrame({
321
+ runId: event.payload.runId,
322
+ frameKey: event.payload.frameKey,
323
+ frameKind: event.payload.frameKind,
324
+ status: "running",
325
+ scope: event.payload.scope ?? {},
326
+ ...(event.payload.instancePath === undefined ? {} : { instancePath: event.payload.instancePath }),
327
+ ...(event.payload.parentFrameKey === undefined ? {} : { parentFrameKey: event.payload.parentFrameKey }),
328
+ ...(event.payload.nodeKey === undefined ? {} : { nodeKey: event.payload.nodeKey }),
329
+ ...(event.payload.nodeId === undefined ? {} : { nodeId: event.payload.nodeId }),
330
+ ...(event.payload.strategy === undefined ? {} : { strategy: event.payload.strategy }),
331
+ });
332
+ return;
333
+ }
334
+ if (event.type === "frame.retry_requested") {
335
+ applyFrameRetryEvent(projection, event.payload.frameKey);
336
+ return;
337
+ }
338
+ if (event.type === "frame.completed" || event.type === "frame.failed" || event.type === "frame.cancelled") {
339
+ const frame = requireKey(projection.frames, event.payload.frameKey, "frame");
340
+ assertFrameOpen(frame);
341
+ if (event.type === "frame.completed") {
342
+ projection.frames[event.payload.frameKey] = compactFrame({ ...frame, status: "completed", ...(event.payload.result === undefined ? {} : { result: event.payload.result }), ...(event.payload.terminalReason === undefined ? {} : { terminalReason: event.payload.terminalReason }) });
343
+ if (event.payload.frameKey === "root")
344
+ projection.run = { ...projection.run, status: "completed" };
345
+ }
346
+ if (event.type === "frame.failed") {
347
+ projection.frames[event.payload.frameKey] = compactFrame({ ...frame, status: "failed", error: event.payload.error, ...(event.payload.terminalReason === undefined ? {} : { terminalReason: event.payload.terminalReason }) });
348
+ if (event.payload.frameKey === "root")
349
+ projection.run = { ...projection.run, status: "failed" };
350
+ }
351
+ if (event.type === "frame.cancelled") {
352
+ projection.frames[event.payload.frameKey] = compactFrame({ ...frame, status: "cancelled", terminalReason: event.payload.cancelReason });
353
+ if (event.payload.frameKey === "root") {
354
+ projection.run = {
355
+ ...projection.run,
356
+ status: event.payload.cancelReason === "operator_cancelled" ? "canceled" : "failed",
357
+ paused: false,
358
+ };
359
+ }
360
+ }
361
+ return;
362
+ }
363
+ if (event.type === "frame.loop_advanced") {
364
+ const frame = requireKey(projection.frames, event.payload.frameKey, "frame");
365
+ assertFrameOpen(frame);
366
+ if (frame.loop) {
367
+ if (event.payload.iter < frame.loop.iter)
368
+ throw new Error(`Loop frame '${frame.frameKey}' cannot move from iteration ${frame.loop.iter} back to ${event.payload.iter}.`);
369
+ if (event.payload.iter > frame.loop.iter) {
370
+ if (event.payload.iter !== frame.loop.iter + 1)
371
+ throw new Error(`Loop frame '${frame.frameKey}' cannot skip from iteration ${frame.loop.iter} to ${event.payload.iter}.`);
372
+ if (frame.loop.result === undefined)
373
+ throw new Error(`Loop frame '${frame.frameKey}' cannot advance before iteration ${frame.loop.iter} has a result.`);
374
+ if (stableJson(event.payload.previous) !== stableJson(frame.loop.result)) {
375
+ throw new Error(`Loop frame '${frame.frameKey}' next previous value must match iteration ${frame.loop.iter} result.`);
376
+ }
377
+ }
378
+ if (event.payload.iter === frame.loop.iter && frame.loop.result !== undefined && event.payload.result !== undefined && stableJson(frame.loop.result) !== stableJson(event.payload.result)) {
379
+ throw new Error(`Loop frame '${frame.frameKey}' already recorded a different result for iteration ${event.payload.iter}.`);
380
+ }
381
+ if (event.payload.iter === frame.loop.iter && stableJson(frame.loop.previous) !== stableJson(event.payload.previous)) {
382
+ throw new Error(`Loop frame '${frame.frameKey}' already recorded a different previous value for iteration ${event.payload.iter}.`);
383
+ }
384
+ }
385
+ if (!frame.loop && event.payload.iter !== 0)
386
+ throw new Error(`Loop frame '${frame.frameKey}' must start at iteration 0.`);
387
+ const sameIter = frame.loop?.iter === event.payload.iter;
388
+ projection.frames[event.payload.frameKey] = compactFrame({
389
+ ...frame,
390
+ loop: {
391
+ iter: event.payload.iter,
392
+ previous: event.payload.previous ?? (sameIter ? frame.loop?.previous : undefined),
393
+ result: event.payload.result ?? (sameIter ? frame.loop?.result : undefined),
394
+ },
395
+ });
396
+ return;
397
+ }
398
+ if (event.type === "instance.ready") {
399
+ if (projection.instances[event.payload.nodeKey])
400
+ throw new Error(`Node instance '${event.payload.nodeKey}' already exists.`);
401
+ projection.instances[event.payload.nodeKey] = compactInstance({
402
+ runId: event.payload.runId,
403
+ nodeKey: event.payload.nodeKey,
404
+ nodeId: event.payload.nodeId,
405
+ status: "ready",
406
+ instancePath: event.payload.instancePath,
407
+ ...(event.payload.parentFrameKey === undefined ? {} : { parentFrameKey: event.payload.parentFrameKey }),
408
+ ...(event.payload.readinessSequence === undefined ? {} : { readinessSequence: event.payload.readinessSequence }),
409
+ });
410
+ return;
411
+ }
412
+ if (isInstanceEvent(event)) {
413
+ applyInstanceEvent(projection, event);
414
+ return;
415
+ }
416
+ if (event.type === "attempt.started") {
417
+ if (projection.attempts[event.payload.attemptId])
418
+ throw new Error(`Attempt '${event.payload.attemptId}' already exists.`);
419
+ projection.attempts[event.payload.attemptId] = compactAttempt({
420
+ runId: event.payload.runId,
421
+ attemptId: event.payload.attemptId,
422
+ nodeKey: event.payload.nodeKey,
423
+ nodeId: event.payload.nodeId,
424
+ attemptNo: event.payload.attemptNo,
425
+ ownerEpoch: event.payload.ownerEpoch,
426
+ status: "started",
427
+ ...(event.payload.deadlineAt === undefined ? {} : { deadlineAt: event.payload.deadlineAt }),
428
+ });
429
+ return;
430
+ }
431
+ if (isAttemptEvent(event)) {
432
+ applyAttemptEvent(projection, event);
433
+ return;
434
+ }
435
+ if (event.type === "group.started") {
436
+ if (projection.groups[event.payload.groupKey])
437
+ throw new Error(`Group '${event.payload.groupKey}' already exists.`);
438
+ assertGroupKindStrategy(event.payload.kind, event.payload.strategy, event.payload.groupKey);
439
+ const quorumCount = event.payload.quorumCount;
440
+ if (event.payload.strategy === "quorum" && (typeof quorumCount !== "number" || !Number.isInteger(quorumCount) || quorumCount <= 0)) {
441
+ throw new Error(`Quorum group '${event.payload.groupKey}' requires a positive quorum count.`);
442
+ }
443
+ projection.groups[event.payload.groupKey] = compactGroup({
444
+ runId: event.payload.runId,
445
+ groupKey: event.payload.groupKey,
446
+ nodeKey: event.payload.nodeKey,
447
+ nodeId: event.payload.nodeId,
448
+ kind: event.payload.kind,
449
+ strategy: event.payload.strategy,
450
+ status: "running",
451
+ ...(quorumCount === undefined ? {} : { quorumCount }),
452
+ });
453
+ return;
454
+ }
455
+ if (isGroupEvent(event)) {
456
+ applyGroupEvent(projection, event);
457
+ return;
458
+ }
459
+ if (event.type === "branch.decided") {
460
+ requireKey(projection.frames, event.payload.frameKey, "frame");
461
+ const existing = projection.branchDecisions[event.payload.frameKey];
462
+ if (existing !== undefined && existing !== event.payload.branchId)
463
+ throw new Error(`Branch decision for frame '${event.payload.frameKey}' is already '${existing}'.`);
464
+ projection.branchDecisions[event.payload.frameKey] = event.payload.branchId;
465
+ return;
466
+ }
467
+ if (isSignalEvent(event)) {
468
+ applySignalEvent(projection, event);
469
+ return;
470
+ }
471
+ assertNever(event);
472
+ }
473
+ function isInstanceEvent(event) {
474
+ switch (event.type) {
475
+ case "instance.ready":
476
+ case "instance.started":
477
+ case "instance.awaiting":
478
+ case "instance.requeued":
479
+ case "instance.retry_requested":
480
+ case "instance.completed":
481
+ case "instance.failed":
482
+ case "instance.cancelled":
483
+ return true;
484
+ default:
485
+ return false;
486
+ }
487
+ }
488
+ function isAttemptEvent(event) {
489
+ switch (event.type) {
490
+ case "attempt.started":
491
+ case "attempt.completed":
492
+ case "attempt.failed":
493
+ case "attempt.timed_out":
494
+ case "attempt.cancelled":
495
+ case "attempt.superseded":
496
+ return true;
497
+ default:
498
+ return false;
499
+ }
500
+ }
501
+ function isGroupEvent(event) {
502
+ switch (event.type) {
503
+ case "group.started":
504
+ case "group.member_ready":
505
+ case "group.member_started":
506
+ case "group.member_requeued":
507
+ case "group.member_retry_requested":
508
+ case "group.member_completed":
509
+ case "group.member_failed":
510
+ case "group.member_cancelled":
511
+ case "group.completed":
512
+ case "group.failed":
513
+ case "group.cancelled":
514
+ return true;
515
+ default:
516
+ return false;
517
+ }
518
+ }
519
+ function isSignalEvent(event) {
520
+ switch (event.type) {
521
+ case "signal.awaiting":
522
+ case "signal.timeout_paused":
523
+ case "signal.timeout_resumed":
524
+ case "signal.consumed":
525
+ case "signal.timed_out":
526
+ case "signal.cancelled":
527
+ return true;
528
+ default:
529
+ return false;
530
+ }
531
+ }
532
+ function assertNever(value) {
533
+ throw new Error(`Unhandled scheduler event: ${String(value)}`);
534
+ }
535
+ function applyInstanceEvent(projection, event) {
536
+ if (!("nodeKey" in event.payload))
537
+ return;
538
+ const instance = requireKey(projection.instances, event.payload.nodeKey, "node instance");
539
+ if (event.type === "instance.requeued") {
540
+ assertInstanceRequeueable(projection, instance);
541
+ projection.instances[event.payload.nodeKey] = compactInstance({
542
+ ...instance,
543
+ status: "ready",
544
+ ...(event.payload.readinessSequence === undefined ? {} : { readinessSequence: event.payload.readinessSequence }),
545
+ statusReason: event.payload.reason,
546
+ });
547
+ return;
548
+ }
549
+ if (event.type === "instance.retry_requested") {
550
+ assertInstanceRetryable(instance);
551
+ if (event.payload.source === "control")
552
+ reopenForControlNodeRetry(projection, instance);
553
+ delete projection.signalWaits[event.payload.nodeKey];
554
+ projection.instances[event.payload.nodeKey] = compactInstance({
555
+ runId: instance.runId,
556
+ nodeKey: instance.nodeKey,
557
+ nodeId: instance.nodeId,
558
+ status: "ready",
559
+ instancePath: instance.instancePath,
560
+ ...(instance.parentFrameKey === undefined ? {} : { parentFrameKey: instance.parentFrameKey }),
561
+ readinessSequence: event.payload.readinessSequence ?? instance.readinessSequence,
562
+ statusReason: event.payload.source === "scheduler" ? "scheduler_retry" : "retry",
563
+ });
564
+ return;
565
+ }
566
+ assertInstanceOpen(instance);
567
+ if (event.type === "instance.started")
568
+ projection.instances[event.payload.nodeKey] = compactInstance({ ...instance, status: "running" });
569
+ if (event.type === "instance.awaiting") {
570
+ projection.instances[event.payload.nodeKey] = compactInstance({ ...instance, status: "awaiting", ...(event.payload.statusReason === undefined ? {} : { statusReason: event.payload.statusReason }) });
571
+ startReadyAncestorMembers(projection, event.payload.nodeKey);
572
+ }
573
+ if (event.type === "instance.completed")
574
+ projection.instances[event.payload.nodeKey] = compactInstance({ ...instance, status: "completed", ...(event.payload.output === undefined ? {} : { output: event.payload.output }), ...(event.payload.acceptedAttemptId === undefined ? {} : { acceptedAttemptId: event.payload.acceptedAttemptId }) });
575
+ if (event.type === "instance.failed")
576
+ projection.instances[event.payload.nodeKey] = compactInstance({ ...instance, status: "failed", error: event.payload.error, ...(event.payload.statusReason === undefined ? {} : { statusReason: event.payload.statusReason }) });
577
+ if (event.type === "instance.cancelled")
578
+ projection.instances[event.payload.nodeKey] = compactInstance({ ...instance, status: "cancelled", statusReason: event.payload.cancelReason });
579
+ }
580
+ function startReadyAncestorMembers(projection, nodeKey) {
581
+ for (const member of ancestorGroupMembersForNode(projection, nodeKey)) {
582
+ if (member.status === "ready")
583
+ projection.groupMembers[member.memberKey] = compactMember({ ...member, status: "running" });
584
+ }
585
+ }
586
+ function applyAttemptEvent(projection, event) {
587
+ if (!("attemptId" in event.payload))
588
+ return;
589
+ const attempt = requireKey(projection.attempts, event.payload.attemptId, "attempt");
590
+ assertAttemptOpen(attempt);
591
+ if (event.type === "attempt.completed")
592
+ projection.attempts[event.payload.attemptId] = compactAttempt({ ...attempt, status: "completed", ...(event.payload.result === undefined ? {} : { result: event.payload.result }) });
593
+ if (event.type === "attempt.failed")
594
+ projection.attempts[event.payload.attemptId] = compactAttempt({ ...attempt, status: "failed", error: event.payload.error, ...(event.payload.terminalReason === undefined ? {} : { terminalReason: event.payload.terminalReason }) });
595
+ if (event.type === "attempt.timed_out")
596
+ projection.attempts[event.payload.attemptId] = compactAttempt({ ...attempt, status: "timed_out", ...(event.payload.error === undefined ? {} : { error: event.payload.error }), terminalReason: "timed_out" });
597
+ if (event.type === "attempt.cancelled")
598
+ projection.attempts[event.payload.attemptId] = compactAttempt({ ...attempt, status: "cancelled", cancelReason: event.payload.cancelReason });
599
+ if (event.type === "attempt.superseded")
600
+ projection.attempts[event.payload.attemptId] = compactAttempt({ ...attempt, status: "superseded", cancelReason: event.payload.cancelReason ?? "superseded" });
601
+ }
602
+ function applyGroupEvent(projection, event) {
603
+ if (event.type === "group.member_ready") {
604
+ if (projection.groupMembers[event.payload.memberKey])
605
+ throw new Error(`Group member '${event.payload.memberKey}' already exists.`);
606
+ const group = requireKey(projection.groups, event.payload.groupKey, "group");
607
+ assertMemberKindMatchesGroup(group, event.payload.memberKind);
608
+ projection.groupMembers[event.payload.memberKey] = {
609
+ runId: event.payload.runId,
610
+ groupKey: event.payload.groupKey,
611
+ memberKey: event.payload.memberKey,
612
+ memberKind: event.payload.memberKind,
613
+ status: "ready",
614
+ readinessSequence: event.payload.readinessSequence,
615
+ ...(event.payload.branchId === undefined ? {} : { branchId: event.payload.branchId }),
616
+ ...(event.payload.itemKey === undefined ? {} : { itemKey: event.payload.itemKey }),
617
+ ...(event.payload.itemIndex === undefined ? {} : { itemIndex: event.payload.itemIndex }),
618
+ ...(event.payload.item === undefined ? {} : { item: event.payload.item }),
619
+ ...(event.payload.childFrameKey === undefined ? {} : { childFrameKey: event.payload.childFrameKey }),
620
+ };
621
+ return;
622
+ }
623
+ if ("memberKey" in event.payload) {
624
+ const member = requireKey(projection.groupMembers, event.payload.memberKey, "group member");
625
+ if (event.type === "group.member_requeued") {
626
+ assertGroupMemberRequeueable(projection, member);
627
+ projection.groupMembers[event.payload.memberKey] = compactMember({
628
+ ...member,
629
+ status: "ready",
630
+ ...(event.payload.readinessSequence === undefined ? {} : { readinessSequence: event.payload.readinessSequence }),
631
+ });
632
+ return;
633
+ }
634
+ if (event.type === "group.member_retry_requested") {
635
+ assertGroupMemberRetryable(member);
636
+ if (event.payload.source === "control")
637
+ reopenGroupForControlRetry(projection, member.groupKey);
638
+ projection.groupMembers[event.payload.memberKey] = compactMember({
639
+ runId: member.runId,
640
+ groupKey: member.groupKey,
641
+ memberKey: member.memberKey,
642
+ memberKind: member.memberKind,
643
+ status: "ready",
644
+ readinessSequence: event.payload.readinessSequence ?? member.readinessSequence,
645
+ ...(member.branchId === undefined ? {} : { branchId: member.branchId }),
646
+ ...(member.itemKey === undefined ? {} : { itemKey: member.itemKey }),
647
+ ...(member.itemIndex === undefined ? {} : { itemIndex: member.itemIndex }),
648
+ ...(member.item === undefined ? {} : { item: member.item }),
649
+ ...(member.childFrameKey === undefined ? {} : { childFrameKey: member.childFrameKey }),
650
+ });
651
+ return;
652
+ }
653
+ assertGroupMemberOpen(member);
654
+ if (event.type === "group.member_started")
655
+ projection.groupMembers[event.payload.memberKey] = compactMember({ ...member, status: "running" });
656
+ if (event.type === "group.member_completed")
657
+ projection.groupMembers[event.payload.memberKey] = compactMember({ ...member, status: "completed", completionSequence: event.payload.completionSequence, ...(event.payload.output === undefined ? {} : { output: event.payload.output }), ...(event.payload.acceptedRank === undefined ? {} : { acceptedRank: event.payload.acceptedRank }) });
658
+ if (event.type === "group.member_failed")
659
+ projection.groupMembers[event.payload.memberKey] = compactMember({ ...member, status: "failed", error: event.payload.error, ...(event.payload.terminalReason === undefined ? {} : { terminalReason: event.payload.terminalReason }) });
660
+ if (event.type === "group.member_cancelled")
661
+ projection.groupMembers[event.payload.memberKey] = compactMember({ ...member, status: "cancelled", terminalReason: event.payload.cancelReason });
662
+ return;
663
+ }
664
+ if ("groupKey" in event.payload) {
665
+ const group = requireKey(projection.groups, event.payload.groupKey, "group");
666
+ assertGroupOpen(group);
667
+ if (event.type === "group.completed")
668
+ projection.groups[event.payload.groupKey] = compactGroup({ ...group, status: "completed", ...(event.payload.result === undefined ? {} : { result: event.payload.result }) });
669
+ if (event.type === "group.failed")
670
+ projection.groups[event.payload.groupKey] = compactGroup({ ...group, status: "failed", error: event.payload.error });
671
+ if (event.type === "group.cancelled")
672
+ projection.groups[event.payload.groupKey] = compactGroup({ ...group, status: "cancelled", error: { reason: event.payload.cancelReason } });
673
+ }
674
+ }
675
+ function reopenForControlNodeRetry(projection, instance) {
676
+ if (projection.run.status === "failed")
677
+ projection.run = { ...projection.run, status: "pending", paused: false };
678
+ reopenFrameForControlRetry(projection, "root");
679
+ for (let frameKey = instance.parentFrameKey; frameKey !== undefined;) {
680
+ const frame = projection.frames[frameKey];
681
+ if (!frame)
682
+ break;
683
+ reopenFrameForControlRetry(projection, frameKey);
684
+ frameKey = frame.parentFrameKey;
685
+ }
686
+ }
687
+ function applyFrameRetryEvent(projection, frameKey) {
688
+ const frame = requireKey(projection.frames, frameKey, "frame");
689
+ assertFrameRetryable(frame);
690
+ const instances = descendantInstancesForFrame(projection, frameKey);
691
+ const instanceKeys = new Set(instances.map(instance => instance.nodeKey));
692
+ const members = descendantGroupMembersForFrame(projection, frameKey);
693
+ const groupKeys = descendantGroupKeysForFrame(projection, frameKey);
694
+ const frames = descendantFramesForFrame(projection, frameKey);
695
+ if (projection.run.status === "failed")
696
+ projection.run = { ...projection.run, status: "pending", paused: false };
697
+ reopenFrameForControlRetry(projection, "root");
698
+ for (let parentKey = frame.parentFrameKey; parentKey !== undefined;) {
699
+ const parent = projection.frames[parentKey];
700
+ if (!parent)
701
+ break;
702
+ reopenFrameForControlRetry(projection, parentKey);
703
+ parentKey = parent.parentFrameKey;
704
+ }
705
+ for (const attempt of Object.values(projection.attempts)) {
706
+ if (instanceKeys.has(attempt.nodeKey))
707
+ delete projection.attempts[attempt.attemptId];
708
+ }
709
+ for (const wait of Object.values(projection.signalWaits)) {
710
+ if (instanceKeys.has(wait.nodeKey))
711
+ delete projection.signalWaits[wait.nodeKey];
712
+ }
713
+ for (const instance of instances)
714
+ delete projection.instances[instance.nodeKey];
715
+ for (const member of members)
716
+ delete projection.groupMembers[member.memberKey];
717
+ for (const groupKey of groupKeys)
718
+ delete projection.groups[groupKey];
719
+ for (const child of frames) {
720
+ delete projection.branchDecisions[child.frameKey];
721
+ delete projection.frames[child.frameKey];
722
+ }
723
+ }
724
+ function reopenFrameForControlRetry(projection, frameKey) {
725
+ const frame = projection.frames[frameKey];
726
+ if (!frame || frame.status !== "failed")
727
+ return;
728
+ projection.frames[frameKey] = compactFrame({
729
+ ...frame,
730
+ status: "running",
731
+ result: undefined,
732
+ error: undefined,
733
+ terminalReason: undefined,
734
+ });
735
+ }
736
+ function reopenGroupForControlRetry(projection, groupKey) {
737
+ const group = projection.groups[groupKey];
738
+ if (!group || group.status !== "failed")
739
+ return;
740
+ projection.groups[groupKey] = compactGroup({
741
+ ...group,
742
+ status: "running",
743
+ result: undefined,
744
+ error: undefined,
745
+ });
746
+ reopenFrameForControlRetry(projection, group.nodeKey);
747
+ }
748
+ function applySignalEvent(projection, event) {
749
+ if (event.type === "signal.awaiting") {
750
+ if (projection.signalWaits[event.payload.nodeKey])
751
+ throw new Error(`Signal wait '${event.payload.nodeKey}' already exists.`);
752
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({
753
+ runId: event.payload.runId,
754
+ nodeKey: event.payload.nodeKey,
755
+ nodeId: event.payload.nodeId,
756
+ status: "awaiting",
757
+ ...(event.payload.deadlineAt === undefined ? {} : { deadlineAt: event.payload.deadlineAt }),
758
+ ...(event.payload.timeoutMessage === undefined ? {} : { timeoutMessage: event.payload.timeoutMessage }),
759
+ ...(event.payload.renderedPrompt === undefined ? {} : { renderedPrompt: event.payload.renderedPrompt }),
760
+ });
761
+ return;
762
+ }
763
+ const wait = requireKey(projection.signalWaits, event.payload.nodeKey, "signal wait");
764
+ if (event.type === "signal.timeout_paused") {
765
+ assertSignalOpen(wait.status, event.payload.nodeKey);
766
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({
767
+ ...wait,
768
+ deadlineAt: undefined,
769
+ timeoutRemainingMs: event.payload.remainingMs,
770
+ });
771
+ return;
772
+ }
773
+ if (event.type === "signal.timeout_resumed") {
774
+ assertSignalOpen(wait.status, event.payload.nodeKey);
775
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({
776
+ ...wait,
777
+ deadlineAt: event.payload.deadlineAt,
778
+ timeoutRemainingMs: undefined,
779
+ });
780
+ return;
781
+ }
782
+ if (event.type === "signal.consumed") {
783
+ if (wait.status === "consumed") {
784
+ if (wait.commandIdempotencyKey === event.payload.commandIdempotencyKey && stableJson(wait.payload) === stableJson(event.payload.payload))
785
+ return;
786
+ throw new Error(`Signal wait '${event.payload.nodeKey}' has already consumed a different payload.`);
787
+ }
788
+ assertSignalOpen(wait.status, event.payload.nodeKey);
789
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({
790
+ ...wait,
791
+ status: "consumed",
792
+ payload: event.payload.payload,
793
+ ...(event.payload.payloadDigest === undefined ? {} : { payloadDigest: event.payload.payloadDigest }),
794
+ ...(event.payload.commandIdempotencyKey === undefined ? {} : { commandIdempotencyKey: event.payload.commandIdempotencyKey }),
795
+ });
796
+ }
797
+ if (event.type === "signal.timed_out") {
798
+ assertSignalOpen(wait.status, event.payload.nodeKey);
799
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({ ...wait, status: "timed_out", ...(event.payload.terminalReason === undefined ? {} : { terminalReason: event.payload.terminalReason }) });
800
+ }
801
+ if (event.type === "signal.cancelled") {
802
+ assertSignalOpen(wait.status, event.payload.nodeKey);
803
+ projection.signalWaits[event.payload.nodeKey] = compactSignalWait({ ...wait, status: "cancelled", terminalReason: event.payload.cancelReason });
804
+ }
805
+ }
806
+ function cloneProjection(projection) {
807
+ return {
808
+ run: { ...projection.run },
809
+ frames: { ...projection.frames },
810
+ instances: { ...projection.instances },
811
+ attempts: { ...projection.attempts },
812
+ groups: { ...projection.groups },
813
+ groupMembers: { ...projection.groupMembers },
814
+ signalWaits: { ...projection.signalWaits },
815
+ branchDecisions: { ...projection.branchDecisions },
816
+ };
817
+ }
818
+ function syncTimingMap(timings, before, after, at, statusOf) {
819
+ for (const key of [...timings.keys()]) {
820
+ if (!(key in after))
821
+ timings.delete(key);
822
+ }
823
+ for (const [key, current] of Object.entries(after)) {
824
+ const previous = before[key];
825
+ if (!previous) {
826
+ timings.set(key, { createdAt: at, updatedAt: at });
827
+ continue;
828
+ }
829
+ if (stableJson(previous) === stableJson(current))
830
+ continue;
831
+ const existing = timings.get(key);
832
+ timings.set(key, {
833
+ createdAt: resetsLifecycle(statusOf(previous), statusOf(current)) ? at : existing?.createdAt ?? at,
834
+ updatedAt: at,
835
+ });
836
+ }
837
+ }
838
+ function resetsLifecycle(previous, current) {
839
+ return terminalProjectionStatus(previous) && !terminalProjectionStatus(current);
840
+ }
841
+ function terminalProjectionStatus(status) {
842
+ return status === "completed" || status === "failed" || status === "cancelled" || status === "canceled" || status === "timed_out" || status === "consumed" || status === "superseded";
843
+ }
844
+ function requireKey(values, key, label) {
845
+ const value = values[key];
846
+ if (!value)
847
+ throw new Error(`Unknown ${label} '${key}'.`);
848
+ return value;
849
+ }
850
+ function byCompletionSequence(left, right) {
851
+ return requireCompletionSequence(left) - requireCompletionSequence(right);
852
+ }
853
+ function requireCompletionSequence(member) {
854
+ if (member.completionSequence === undefined)
855
+ throw new Error(`Completed group member '${member.memberKey}' is missing completion sequence.`);
856
+ return member.completionSequence;
857
+ }
858
+ function requirePositiveQuorum(group) {
859
+ const quorum = group.quorumCount;
860
+ if (typeof quorum !== "number" || !Number.isInteger(quorum) || quorum <= 0)
861
+ throw new Error(`Quorum group '${group.groupKey}' requires a positive quorum count.`);
862
+ return quorum;
863
+ }
864
+ function assertFrameOpen(frame) {
865
+ if (isTerminalFrameStatus(frame.status))
866
+ throw new Error(`Frame '${frame.frameKey}' is already ${frame.status}.`);
867
+ }
868
+ function assertFrameRetryable(frame) {
869
+ if (frame.status !== "failed")
870
+ throw new Error(`Frame '${frame.frameKey}' cannot be retried from ${frame.status}.`);
871
+ if (frame.frameKind !== "node" && frame.frameKind !== "loop")
872
+ throw new Error(`Frame '${frame.frameKey}' is not a retryable public node frame.`);
873
+ }
874
+ function assertInstanceOpen(instance) {
875
+ if (isTerminalInstanceStatus(instance.status))
876
+ throw new Error(`Node instance '${instance.nodeKey}' is already ${instance.status}.`);
877
+ }
878
+ function assertInstanceRequeueable(projection, instance) {
879
+ if (instance.status !== "running" && instance.status !== "awaiting")
880
+ throw new Error(`Node instance '${instance.nodeKey}' cannot be requeued from ${instance.status}.`);
881
+ assertNoStartedAttemptForNode(projection, instance.nodeKey);
882
+ if (projection.signalWaits[instance.nodeKey]?.status === "awaiting") {
883
+ throw new Error(`Node instance '${instance.nodeKey}' cannot be requeued while a signal wait is awaiting.`);
884
+ }
885
+ }
886
+ function assertInstanceRetryable(instance) {
887
+ if (instance.status !== "failed")
888
+ throw new Error(`Node instance '${instance.nodeKey}' cannot be retried from ${instance.status}.`);
889
+ }
890
+ function assertAttemptOpen(attempt) {
891
+ if (isTerminalAttemptStatus(attempt.status))
892
+ throw new Error(`Attempt '${attempt.attemptId}' is already ${attempt.status}.`);
893
+ }
894
+ function assertGroupOpen(group) {
895
+ if (group.status !== "running")
896
+ throw new Error(`Group '${group.groupKey}' is already ${group.status}.`);
897
+ }
898
+ function assertGroupMemberOpen(member) {
899
+ if (isTerminalGroupMemberStatus(member.status))
900
+ throw new Error(`Group member '${member.memberKey}' is already ${member.status}.`);
901
+ }
902
+ function assertGroupMemberRequeueable(projection, member) {
903
+ if (member.status !== "running")
904
+ throw new Error(`Group member '${member.memberKey}' cannot be requeued from ${member.status}.`);
905
+ assertNoStartedAttemptForNode(projection, member.memberKey);
906
+ }
907
+ function assertGroupMemberRetryable(member) {
908
+ if (member.status !== "failed")
909
+ throw new Error(`Group member '${member.memberKey}' cannot be retried from ${member.status}.`);
910
+ }
911
+ function assertNoStartedAttemptForNode(projection, nodeKey) {
912
+ if (Object.values(projection.attempts).some(attempt => attempt.nodeKey === nodeKey && attempt.status === "started")) {
913
+ throw new Error(`Node instance '${nodeKey}' cannot be requeued while an attempt is still started.`);
914
+ }
915
+ }
916
+ function assertSignalOpen(status, nodeKey) {
917
+ if (status !== "awaiting")
918
+ throw new Error(`Signal wait '${nodeKey}' is already ${status}.`);
919
+ }
920
+ function assertRunControllable(status, action) {
921
+ if (status === "completed" || status === "failed" || status === "canceled")
922
+ throw new Error(`Cannot ${action} ${status} run.`);
923
+ }
924
+ function assertGroupKindStrategy(kind, strategy, groupKey) {
925
+ if (kind === "parallel" && strategy !== "all" && strategy !== "race")
926
+ throw new Error(`Parallel group '${groupKey}' does not support ${strategy}.`);
927
+ if (kind === "fanout" && strategy !== "all" && strategy !== "quorum")
928
+ throw new Error(`Fanout group '${groupKey}' does not support ${strategy}.`);
929
+ }
930
+ function assertMemberKindMatchesGroup(group, memberKind) {
931
+ if (group.kind === "parallel" && memberKind !== "branch")
932
+ throw new Error(`Parallel group '${group.groupKey}' requires branch members.`);
933
+ if (group.kind === "fanout" && memberKind !== "fanout_item")
934
+ throw new Error(`Fanout group '${group.groupKey}' requires fanout item members.`);
935
+ }
936
+ function stableJson(value) {
937
+ return JSON.stringify(sortJson(value));
938
+ }
939
+ function sortJson(value) {
940
+ if (Array.isArray(value))
941
+ return value.map(sortJson);
942
+ if (!value || typeof value !== "object")
943
+ return value;
944
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, field]) => [key, sortJson(field)]));
945
+ }
946
+ function compactFrame(frame) {
947
+ return dropUndefined(frame);
948
+ }
949
+ function compactInstance(instance) {
950
+ return dropUndefined(instance);
951
+ }
952
+ function compactAttempt(attempt) {
953
+ return dropUndefined(attempt);
954
+ }
955
+ function compactGroup(group) {
956
+ return dropUndefined(group);
957
+ }
958
+ function compactMember(member) {
959
+ return dropUndefined(member);
960
+ }
961
+ function compactSignalWait(wait) {
962
+ return dropUndefined(wait);
963
+ }
964
+ function dropUndefined(value) {
965
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined));
966
+ }
967
+ //# sourceMappingURL=transitions.js.map