@caupulican/pi-adaptative 0.86.2 → 0.86.5

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 (226) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/core/agent-paths.d.ts +13 -0
  3. package/dist/core/agent-paths.d.ts.map +1 -1
  4. package/dist/core/agent-paths.js +23 -0
  5. package/dist/core/agent-paths.js.map +1 -1
  6. package/dist/core/autonomy/bounded-completion.d.ts +7 -0
  7. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  8. package/dist/core/autonomy/bounded-completion.js +12 -4
  9. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  10. package/dist/core/autonomy/lane-tool-surface.d.ts +2 -0
  11. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  12. package/dist/core/autonomy/lane-tool-surface.js +6 -3
  13. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  14. package/dist/core/autonomy/subagent-prompt.js +1 -1
  15. package/dist/core/autonomy/subagent-prompt.js.map +1 -1
  16. package/dist/core/background-lane-controller.d.ts +25 -2
  17. package/dist/core/background-lane-controller.d.ts.map +1 -1
  18. package/dist/core/background-lane-controller.js +67 -0
  19. package/dist/core/background-lane-controller.js.map +1 -1
  20. package/dist/core/delegation/replay-safe-mailbox-bounds.d.ts +4 -0
  21. package/dist/core/delegation/replay-safe-mailbox-bounds.d.ts.map +1 -0
  22. package/dist/core/delegation/replay-safe-mailbox-bounds.js +20 -0
  23. package/dist/core/delegation/replay-safe-mailbox-bounds.js.map +1 -0
  24. package/dist/core/delegation/sanitized-context-fork.d.ts +25 -0
  25. package/dist/core/delegation/sanitized-context-fork.d.ts.map +1 -0
  26. package/dist/core/delegation/sanitized-context-fork.js +299 -0
  27. package/dist/core/delegation/sanitized-context-fork.js.map +1 -0
  28. package/dist/core/delegation/session-root-mailbox.d.ts +75 -0
  29. package/dist/core/delegation/session-root-mailbox.d.ts.map +1 -0
  30. package/dist/core/delegation/session-root-mailbox.js +627 -0
  31. package/dist/core/delegation/session-root-mailbox.js.map +1 -0
  32. package/dist/core/delegation/worker-agent-control-coordinator.d.ts +102 -9
  33. package/dist/core/delegation/worker-agent-control-coordinator.d.ts.map +1 -1
  34. package/dist/core/delegation/worker-agent-control-coordinator.js +1307 -125
  35. package/dist/core/delegation/worker-agent-control-coordinator.js.map +1 -1
  36. package/dist/core/delegation/worker-agent-control.d.ts +183 -8
  37. package/dist/core/delegation/worker-agent-control.d.ts.map +1 -1
  38. package/dist/core/delegation/worker-agent-control.js +778 -48
  39. package/dist/core/delegation/worker-agent-control.js.map +1 -1
  40. package/dist/core/delegation/worker-attempt-executor.d.ts +9 -0
  41. package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -1
  42. package/dist/core/delegation/worker-attempt-executor.js +479 -214
  43. package/dist/core/delegation/worker-attempt-executor.js.map +1 -1
  44. package/dist/core/delegation/worker-authority-resolver.d.ts +9 -0
  45. package/dist/core/delegation/worker-authority-resolver.d.ts.map +1 -1
  46. package/dist/core/delegation/worker-authority-resolver.js +22 -14
  47. package/dist/core/delegation/worker-authority-resolver.js.map +1 -1
  48. package/dist/core/delegation/worker-context-fork-store.d.ts +71 -0
  49. package/dist/core/delegation/worker-context-fork-store.d.ts.map +1 -0
  50. package/dist/core/delegation/worker-context-fork-store.js +437 -0
  51. package/dist/core/delegation/worker-context-fork-store.js.map +1 -0
  52. package/dist/core/delegation/worker-context-inheritance-policy.d.ts +18 -0
  53. package/dist/core/delegation/worker-context-inheritance-policy.d.ts.map +1 -0
  54. package/dist/core/delegation/worker-context-inheritance-policy.js +28 -0
  55. package/dist/core/delegation/worker-context-inheritance-policy.js.map +1 -0
  56. package/dist/core/delegation/worker-conversation-revision.d.ts +31 -0
  57. package/dist/core/delegation/worker-conversation-revision.d.ts.map +1 -0
  58. package/dist/core/delegation/worker-conversation-revision.js +203 -0
  59. package/dist/core/delegation/worker-conversation-revision.js.map +1 -0
  60. package/dist/core/delegation/worker-conversation-store.d.ts +130 -18
  61. package/dist/core/delegation/worker-conversation-store.d.ts.map +1 -1
  62. package/dist/core/delegation/worker-conversation-store.js +1263 -79
  63. package/dist/core/delegation/worker-conversation-store.js.map +1 -1
  64. package/dist/core/delegation/worker-delegation-controller.d.ts +29 -2
  65. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
  66. package/dist/core/delegation/worker-delegation-controller.js +625 -111
  67. package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
  68. package/dist/core/delegation/worker-delegation-request.d.ts +2 -0
  69. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
  70. package/dist/core/delegation/worker-delegation-request.js.map +1 -1
  71. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +20 -1
  72. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -1
  73. package/dist/core/delegation/worker-dispatch-scheduler.js +210 -37
  74. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -1
  75. package/dist/core/delegation/worker-fleet-limits.d.ts +38 -0
  76. package/dist/core/delegation/worker-fleet-limits.d.ts.map +1 -0
  77. package/dist/core/delegation/worker-fleet-limits.js +113 -0
  78. package/dist/core/delegation/worker-fleet-limits.js.map +1 -0
  79. package/dist/core/delegation/worker-lane-projection.d.ts +1 -0
  80. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -1
  81. package/dist/core/delegation/worker-lane-projection.js +6 -2
  82. package/dist/core/delegation/worker-lane-projection.js.map +1 -1
  83. package/dist/core/delegation/worker-lifecycle.d.ts +20 -3
  84. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -1
  85. package/dist/core/delegation/worker-lifecycle.js +42 -18
  86. package/dist/core/delegation/worker-lifecycle.js.map +1 -1
  87. package/dist/core/delegation/worker-notification-coordinator.d.ts +4 -0
  88. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -1
  89. package/dist/core/delegation/worker-notification-coordinator.js +45 -4
  90. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -1
  91. package/dist/core/delegation/worker-provider-turn-protocol.d.ts +50 -0
  92. package/dist/core/delegation/worker-provider-turn-protocol.d.ts.map +1 -0
  93. package/dist/core/delegation/worker-provider-turn-protocol.js +185 -0
  94. package/dist/core/delegation/worker-provider-turn-protocol.js.map +1 -0
  95. package/dist/core/delegation/worker-recovery-coordinator.d.ts +45 -4
  96. package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -1
  97. package/dist/core/delegation/worker-recovery-coordinator.js +223 -17
  98. package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -1
  99. package/dist/core/delegation/worker-retry-policy.d.ts +36 -0
  100. package/dist/core/delegation/worker-retry-policy.d.ts.map +1 -0
  101. package/dist/core/delegation/worker-retry-policy.js +41 -0
  102. package/dist/core/delegation/worker-retry-policy.js.map +1 -0
  103. package/dist/core/delegation/worker-runner.d.ts +3 -1
  104. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  105. package/dist/core/delegation/worker-runner.js +4 -2
  106. package/dist/core/delegation/worker-runner.js.map +1 -1
  107. package/dist/core/delegation/worker-task-view.d.ts +43 -0
  108. package/dist/core/delegation/worker-task-view.d.ts.map +1 -0
  109. package/dist/core/delegation/worker-task-view.js +194 -0
  110. package/dist/core/delegation/worker-task-view.js.map +1 -0
  111. package/dist/core/delegation/worker-terminal-handoff-coordinator.d.ts +55 -0
  112. package/dist/core/delegation/worker-terminal-handoff-coordinator.d.ts.map +1 -0
  113. package/dist/core/delegation/worker-terminal-handoff-coordinator.js +179 -0
  114. package/dist/core/delegation/worker-terminal-handoff-coordinator.js.map +1 -0
  115. package/dist/core/delegation/worker-tree-budget-coordinator.d.ts +2 -2
  116. package/dist/core/delegation/worker-tree-budget-coordinator.d.ts.map +1 -1
  117. package/dist/core/delegation/worker-tree-budget-coordinator.js +12 -46
  118. package/dist/core/delegation/worker-tree-budget-coordinator.js.map +1 -1
  119. package/dist/core/keybindings.d.ts +10 -0
  120. package/dist/core/keybindings.d.ts.map +1 -1
  121. package/dist/core/keybindings.js +2 -0
  122. package/dist/core/keybindings.js.map +1 -1
  123. package/dist/core/orchestration/attempt-ordering.d.ts +9 -0
  124. package/dist/core/orchestration/attempt-ordering.d.ts.map +1 -0
  125. package/dist/core/orchestration/attempt-ordering.js +29 -0
  126. package/dist/core/orchestration/attempt-ordering.js.map +1 -0
  127. package/dist/core/orchestration/capability-gateway.d.ts +19 -0
  128. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -1
  129. package/dist/core/orchestration/capability-gateway.js +19 -2
  130. package/dist/core/orchestration/capability-gateway.js.map +1 -1
  131. package/dist/core/orchestration/contracts.d.ts +43 -3
  132. package/dist/core/orchestration/contracts.d.ts.map +1 -1
  133. package/dist/core/orchestration/contracts.js +24 -0
  134. package/dist/core/orchestration/contracts.js.map +1 -1
  135. package/dist/core/orchestration/delegation-ledger.d.ts +8 -3
  136. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -1
  137. package/dist/core/orchestration/delegation-ledger.js +148 -55
  138. package/dist/core/orchestration/delegation-ledger.js.map +1 -1
  139. package/dist/core/orchestration/event-store.d.ts +6 -3
  140. package/dist/core/orchestration/event-store.d.ts.map +1 -1
  141. package/dist/core/orchestration/event-store.js +228 -30
  142. package/dist/core/orchestration/event-store.js.map +1 -1
  143. package/dist/core/orchestration/task-runtime-codecs.d.ts +31 -0
  144. package/dist/core/orchestration/task-runtime-codecs.d.ts.map +1 -0
  145. package/dist/core/orchestration/task-runtime-codecs.js +1471 -0
  146. package/dist/core/orchestration/task-runtime-codecs.js.map +1 -0
  147. package/dist/core/orchestration/task-runtime-projection.d.ts +30 -0
  148. package/dist/core/orchestration/task-runtime-projection.d.ts.map +1 -0
  149. package/dist/core/orchestration/task-runtime-projection.js +340 -0
  150. package/dist/core/orchestration/task-runtime-projection.js.map +1 -0
  151. package/dist/core/orchestration/task-runtime-reducer.d.ts +38 -0
  152. package/dist/core/orchestration/task-runtime-reducer.d.ts.map +1 -0
  153. package/dist/core/orchestration/task-runtime-reducer.js +1270 -0
  154. package/dist/core/orchestration/task-runtime-reducer.js.map +1 -0
  155. package/dist/core/orchestration/task-runtime-state.d.ts +142 -0
  156. package/dist/core/orchestration/task-runtime-state.d.ts.map +1 -0
  157. package/dist/core/orchestration/task-runtime-state.js +14 -0
  158. package/dist/core/orchestration/task-runtime-state.js.map +1 -0
  159. package/dist/core/orchestration/task-runtime.d.ts +44 -98
  160. package/dist/core/orchestration/task-runtime.d.ts.map +1 -1
  161. package/dist/core/orchestration/task-runtime.js +357 -1113
  162. package/dist/core/orchestration/task-runtime.js.map +1 -1
  163. package/dist/core/orchestration/worker-context-fork-reference.d.ts +20 -0
  164. package/dist/core/orchestration/worker-context-fork-reference.d.ts.map +1 -0
  165. package/dist/core/orchestration/worker-context-fork-reference.js +53 -0
  166. package/dist/core/orchestration/worker-context-fork-reference.js.map +1 -0
  167. package/dist/core/research/research-runner.d.ts +2 -0
  168. package/dist/core/research/research-runner.d.ts.map +1 -1
  169. package/dist/core/research/research-runner.js +1 -0
  170. package/dist/core/research/research-runner.js.map +1 -1
  171. package/dist/core/runtime-builder.d.ts.map +1 -1
  172. package/dist/core/runtime-builder.js +8 -0
  173. package/dist/core/runtime-builder.js.map +1 -1
  174. package/dist/core/secrets/credential-project.d.ts.map +1 -1
  175. package/dist/core/secrets/credential-project.js +24 -2
  176. package/dist/core/secrets/credential-project.js.map +1 -1
  177. package/dist/core/security/secret-text.d.ts +3 -0
  178. package/dist/core/security/secret-text.d.ts.map +1 -1
  179. package/dist/core/security/secret-text.js +15 -0
  180. package/dist/core/security/secret-text.js.map +1 -1
  181. package/dist/core/settings-manager.d.ts +3 -0
  182. package/dist/core/settings-manager.d.ts.map +1 -1
  183. package/dist/core/settings-manager.js +160 -8
  184. package/dist/core/settings-manager.js.map +1 -1
  185. package/dist/core/tools/delegate.d.ts +31 -4
  186. package/dist/core/tools/delegate.d.ts.map +1 -1
  187. package/dist/core/tools/delegate.js +751 -98
  188. package/dist/core/tools/delegate.js.map +1 -1
  189. package/dist/main.d.ts.map +1 -1
  190. package/dist/main.js +1 -0
  191. package/dist/main.js.map +1 -1
  192. package/dist/modes/interactive/components/activity-lane.d.ts +5 -0
  193. package/dist/modes/interactive/components/activity-lane.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/activity-lane.js +133 -49
  195. package/dist/modes/interactive/components/activity-lane.js.map +1 -1
  196. package/dist/modes/interactive/components/agents-overlay.d.ts +40 -0
  197. package/dist/modes/interactive/components/agents-overlay.d.ts.map +1 -0
  198. package/dist/modes/interactive/components/agents-overlay.js +153 -0
  199. package/dist/modes/interactive/components/agents-overlay.js.map +1 -0
  200. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/settings-selector.js +18 -13
  202. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  203. package/dist/modes/interactive/interactive-event-controller.d.ts +1 -1
  204. package/dist/modes/interactive/interactive-event-controller.d.ts.map +1 -1
  205. package/dist/modes/interactive/interactive-event-controller.js +6 -3
  206. package/dist/modes/interactive/interactive-event-controller.js.map +1 -1
  207. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  208. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  209. package/dist/modes/interactive/interactive-mode.js +51 -0
  210. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  211. package/dist/modes/interactive/key-handlers.d.ts +1 -0
  212. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  213. package/dist/modes/interactive/key-handlers.js +1 -0
  214. package/dist/modes/interactive/key-handlers.js.map +1 -1
  215. package/docs/harness-architecture.md +61 -17
  216. package/docs/settings.md +17 -9
  217. package/docs/worker-profiles.md +6 -2
  218. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  219. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  220. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  221. package/examples/extensions/sandbox/package-lock.json +2 -2
  222. package/examples/extensions/sandbox/package.json +1 -1
  223. package/examples/extensions/with-deps/package-lock.json +2 -2
  224. package/examples/extensions/with-deps/package.json +1 -1
  225. package/npm-shrinkwrap.json +12 -12
  226. package/package.json +4 -4
@@ -0,0 +1,1270 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { MAX_ORCHESTRATION_AGENT_BINDINGS, MAX_ORCHESTRATION_APPROVALS, MAX_ORCHESTRATION_ATTEMPTS, MAX_ORCHESTRATION_CHECKPOINTS, MAX_ORCHESTRATION_NOTIFICATIONS, MAX_ORCHESTRATION_OBJECTIVES, MAX_ORCHESTRATION_TASKS, ORCHESTRATION_SCHEMA_VERSION, WORKER_RESULT_STATUSES, } from "./contracts.js";
3
+ import { agentFromPayload, approvalFromPayload, approvalResolutionFromPayload, assertReferencedAcceptanceCriteriaRetained, checkpointFromPayload, dispatchFromValue, dispatchIdentifier, evidenceFromPayload, executionGrantFromValue, leaseFromPayload, number, objectiveFromPayload, resultFromPayload, retryStateFromValue, string, taskFromPayload, validateTaskContractForState, } from "./task-runtime-codecs.js";
4
+ import { assertIdentifierListHasCapacity, assertObjectiveEvidenceHasCapacity, assertProjectionWithinLimits, assertRecordHasCapacity, cacheProjectionSerializedBytes, cloneProjection, emptyProjection, ProjectionByteTracker, } from "./task-runtime-projection.js";
5
+ import { DurableTaskRuntimeError, missingTrustedCriteria, terminalAttemptStatus, } from "./task-runtime-state.js";
6
+ function updateObjectiveStatus(state, objectiveId, status, at) {
7
+ const current = state.objectives[objectiveId];
8
+ if (!current)
9
+ throw new DurableTaskRuntimeError(`Unknown objective '${objectiveId}'.`);
10
+ state.objectives[objectiveId] = {
11
+ ...current,
12
+ objective: { ...current.objective, status, updatedAt: at },
13
+ };
14
+ }
15
+ function assertObjectiveUpdateTransition(state, aggregateId, objective) {
16
+ const current = state.objectives[aggregateId];
17
+ if (!current)
18
+ throw new DurableTaskRuntimeError(`Unknown objective '${aggregateId}'.`);
19
+ if (objective.objectiveId !== aggregateId) {
20
+ throw new DurableTaskRuntimeError(`Updated objective id '${objective.objectiveId}' does not match aggregate.`);
21
+ }
22
+ if (objective.schemaVersion !== current.objective.schemaVersion ||
23
+ objective.status !== current.objective.status ||
24
+ objective.createdAt !== current.objective.createdAt) {
25
+ throw new DurableTaskRuntimeError(`Updated objective '${aggregateId}' changed immutable lifecycle fields.`);
26
+ }
27
+ assertReferencedAcceptanceCriteriaRetained(state.tasks, current, objective.acceptanceCriteria);
28
+ return current;
29
+ }
30
+ function assertObjectiveEvidenceTransition(state, objectiveId, evidence) {
31
+ const objective = state.objectives[objectiveId];
32
+ if (!objective)
33
+ throw new DurableTaskRuntimeError(`Unknown objective '${objectiveId}'.`);
34
+ if (evidence.criterionId &&
35
+ !objective.objective.acceptanceCriteria.some((criterion) => criterion.id === evidence.criterionId)) {
36
+ throw new DurableTaskRuntimeError(`Objective evidence references unknown acceptance criterion '${evidence.criterionId}'.`);
37
+ }
38
+ return objective;
39
+ }
40
+ function assertObjectiveCompletion(state, objectiveId, policy) {
41
+ const objective = state.objectives[objectiveId];
42
+ if (!objective)
43
+ throw new DurableTaskRuntimeError(`Unknown objective '${objectiveId}'.`);
44
+ if (policy === "owner_override")
45
+ return;
46
+ if (policy === "task_evidence") {
47
+ const incomplete = objective.taskIds.filter((taskId) => state.tasks[taskId]?.task.status !== "completed");
48
+ if (incomplete.length > 0) {
49
+ throw new DurableTaskRuntimeError(`Objective '${objectiveId}' has incomplete tasks: ${incomplete.join(", ")}.`);
50
+ }
51
+ }
52
+ const evidence = policy === "owner_evidence"
53
+ ? objective.evidence
54
+ : [
55
+ ...objective.evidence,
56
+ ...objective.taskIds.flatMap((taskId) => (state.tasks[taskId]?.attemptIds ?? []).flatMap((attemptId) => state.attempts[attemptId]?.result?.evidence ?? [])),
57
+ ];
58
+ const provenCriterionIds = new Set(evidence.flatMap((item) => (item.trusted && item.criterionId ? [item.criterionId] : [])));
59
+ const unproven = objective.objective.acceptanceCriteria
60
+ .filter((criterion) => criterion.required && !provenCriterionIds.has(criterion.id))
61
+ .map((criterion) => criterion.id);
62
+ if (unproven.length > 0) {
63
+ throw new DurableTaskRuntimeError(`Objective '${objectiveId}' lacks trusted ${policy === "owner_evidence" ? "owner " : ""}evidence for required criteria: ${unproven.join(", ")}.`);
64
+ }
65
+ }
66
+ export function assertObjectiveStatusTransition(state, objectiveId, target, completionPolicy) {
67
+ const objective = state.objectives[objectiveId]?.objective;
68
+ if (!objective)
69
+ throw new DurableTaskRuntimeError(`Unknown objective '${objectiveId}'.`);
70
+ const validSource = (target === "paused" && objective.status === "active") ||
71
+ (target === "active" && objective.status === "paused") ||
72
+ ((target === "completed" || target === "cancelled") &&
73
+ (objective.status === "active" || objective.status === "paused"));
74
+ if (!validSource) {
75
+ throw new DurableTaskRuntimeError(`Objective '${objectiveId}' cannot transition from '${objective.status}' to '${target}'.`);
76
+ }
77
+ if (target === "completed") {
78
+ if (!completionPolicy)
79
+ throw new DurableTaskRuntimeError("Objective completion policy is required.");
80
+ assertObjectiveCompletion(state, objectiveId, completionPolicy);
81
+ }
82
+ }
83
+ const RETIREMENT_BLOCKING_ATTEMPT_STATUSES = new Set(["queued", "leased", "running", "suspended"]);
84
+ function isAgentDescendant(agents, candidate, ancestorAgentId) {
85
+ let parentAgentId = candidate.parentAgentId;
86
+ const visited = new Set();
87
+ while (parentAgentId) {
88
+ if (parentAgentId === ancestorAgentId)
89
+ return true;
90
+ if (visited.has(parentAgentId)) {
91
+ throw new DurableTaskRuntimeError(`Agent '${candidate.agentId}' lineage contains a cycle.`);
92
+ }
93
+ visited.add(parentAgentId);
94
+ parentAgentId = agents[parentAgentId]?.parentAgentId;
95
+ }
96
+ return false;
97
+ }
98
+ /** One authoritative guard shared by command admission and durable event replay. */
99
+ export function assertAgentRetirementEligible(state, agentId) {
100
+ const agent = state.agents[agentId];
101
+ if (!agent)
102
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
103
+ if (agent.status === "retired")
104
+ return agent;
105
+ if (agent.status !== "registered") {
106
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' cannot retire from '${agent.status}'.`);
107
+ }
108
+ if (agent.activeAttemptId) {
109
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' still owns active attempt '${agent.activeAttemptId}'.`);
110
+ }
111
+ const blockingAttempt = Object.values(state.attempts).find((attempt) => (attempt.agentId === agentId || attempt.dispatch.logicalLaneId === agentId) &&
112
+ RETIREMENT_BLOCKING_ATTEMPT_STATUSES.has(attempt.status));
113
+ if (blockingAttempt) {
114
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' owns active '${blockingAttempt.status}' attempt '${blockingAttempt.attemptId}'.`);
115
+ }
116
+ const liveDescendant = Object.values(state.agents).find((candidate) => candidate.status !== "retired" &&
117
+ candidate.agentId !== agentId &&
118
+ isAgentDescendant(state.agents, candidate, agentId));
119
+ if (liveDescendant) {
120
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' has non-retired descendant '${liveDescendant.agentId}'.`);
121
+ }
122
+ return agent;
123
+ }
124
+ export function assertAgentNotRetired(agent, transition) {
125
+ if (agent.status === "retired") {
126
+ throw new DurableTaskRuntimeError(`Agent '${agent.agentId}' is retired and cannot ${transition}.`);
127
+ }
128
+ }
129
+ function withoutAttemptRetry(attempt) {
130
+ const next = { ...attempt };
131
+ delete next.retry;
132
+ return next;
133
+ }
134
+ function cancelOpenObjectiveWork(state, objectiveId, at, reasonCode) {
135
+ const objective = state.objectives[objectiveId];
136
+ const tasks = state.tasks;
137
+ const attempts = state.attempts;
138
+ const agents = state.agents;
139
+ for (const taskId of objective?.taskIds ?? []) {
140
+ const taskState = tasks[taskId];
141
+ if (taskState && !["completed", "failed", "cancelled"].includes(taskState.task.status)) {
142
+ tasks[taskId] = {
143
+ ...taskState,
144
+ task: { ...taskState.task, status: "cancelled", updatedAt: at },
145
+ };
146
+ }
147
+ for (const attemptId of taskState?.attemptIds ?? []) {
148
+ const attempt = attempts[attemptId];
149
+ if (attempt && !terminalAttemptStatus(attempt.status)) {
150
+ attempts[attemptId] = withoutAttemptRetry({
151
+ ...attempt,
152
+ status: "cancelled",
153
+ reasonCode,
154
+ updatedAt: at,
155
+ });
156
+ releaseAttemptAgent(agents, attempt, at);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ function taskStatusForResult(status) {
162
+ if (status === "completed")
163
+ return "completed";
164
+ if (status === "failed")
165
+ return "failed";
166
+ if (status === "cancelled")
167
+ return "cancelled";
168
+ return "blocked";
169
+ }
170
+ function releaseAttemptAgent(agents, attempt, at) {
171
+ if (!attempt.agentId)
172
+ return;
173
+ const agent = agents[attempt.agentId];
174
+ if (!agent)
175
+ return;
176
+ assertAgentNotRetired(agent, "release an attempt");
177
+ const next = { ...agent, status: "registered", updatedAt: at };
178
+ delete next.activeAttemptId;
179
+ agents[attempt.agentId] = next;
180
+ }
181
+ function taskDependencyReadiness(state, task) {
182
+ const dependencyTaskIds = [];
183
+ const failedDependencyTaskIds = [];
184
+ const cancelledDependencyTaskIds = [];
185
+ for (const dependencyId of task.task.dependsOn) {
186
+ const dependency = state.tasks[dependencyId];
187
+ if (!dependency || dependency.task.objectiveId !== task.task.objectiveId) {
188
+ throw new DurableTaskRuntimeError(`Task dependency '${dependencyId}' is not in objective '${task.task.objectiveId}'.`);
189
+ }
190
+ if (dependency.task.status === "completed")
191
+ continue;
192
+ dependencyTaskIds.push(dependencyId);
193
+ if (dependency.task.status === "failed")
194
+ failedDependencyTaskIds.push(dependencyId);
195
+ if (dependency.task.status === "cancelled")
196
+ cancelledDependencyTaskIds.push(dependencyId);
197
+ }
198
+ if (failedDependencyTaskIds.length > 0 || cancelledDependencyTaskIds.length > 0) {
199
+ return {
200
+ state: "blocked",
201
+ dependencyTaskIds,
202
+ failedDependencyTaskIds,
203
+ cancelledDependencyTaskIds,
204
+ };
205
+ }
206
+ return dependencyTaskIds.length > 0 ? { state: "waiting", dependencyTaskIds } : { state: "ready" };
207
+ }
208
+ export function attemptDispatchReadiness(state, attempt) {
209
+ if (attempt.status !== "queued" && attempt.status !== "suspended") {
210
+ throw new DurableTaskRuntimeError(`Attempt '${attempt.attemptId}' is neither queued nor suspended.`);
211
+ }
212
+ const task = state.tasks[attempt.taskId];
213
+ if (!task)
214
+ throw new DurableTaskRuntimeError(`Unknown task '${attempt.taskId}'.`);
215
+ const objective = state.objectives[task.task.objectiveId]?.objective;
216
+ if (!objective)
217
+ throw new DurableTaskRuntimeError(`Unknown objective '${task.task.objectiveId}'.`);
218
+ if (objective.status === "paused") {
219
+ return {
220
+ state: "waiting",
221
+ reasonCode: "objective_paused",
222
+ attemptId: attempt.attemptId,
223
+ taskId: attempt.taskId,
224
+ objectiveStatus: "paused",
225
+ };
226
+ }
227
+ if (objective.status !== "active") {
228
+ return {
229
+ state: "blocked",
230
+ reasonCode: "objective_inactive",
231
+ attemptId: attempt.attemptId,
232
+ taskId: attempt.taskId,
233
+ objectiveStatus: objective.status,
234
+ };
235
+ }
236
+ const dependencyReadiness = taskDependencyReadiness(state, task);
237
+ if (dependencyReadiness.state === "blocked") {
238
+ return {
239
+ state: "blocked",
240
+ reasonCode: "dependency_failed_or_cancelled",
241
+ attemptId: attempt.attemptId,
242
+ taskId: attempt.taskId,
243
+ dependencyTaskIds: dependencyReadiness.dependencyTaskIds,
244
+ failedDependencyTaskIds: dependencyReadiness.failedDependencyTaskIds,
245
+ cancelledDependencyTaskIds: dependencyReadiness.cancelledDependencyTaskIds,
246
+ };
247
+ }
248
+ if (dependencyReadiness.state === "waiting") {
249
+ return {
250
+ state: "waiting",
251
+ reasonCode: "dependencies_incomplete",
252
+ attemptId: attempt.attemptId,
253
+ taskId: attempt.taskId,
254
+ dependencyTaskIds: dependencyReadiness.dependencyTaskIds,
255
+ };
256
+ }
257
+ return { state: "ready", attemptId: attempt.attemptId, taskId: attempt.taskId };
258
+ }
259
+ function assertAttemptDispatchReady(state, attempt) {
260
+ const readiness = attemptDispatchReadiness(state, attempt);
261
+ if (readiness.state === "ready") {
262
+ const task = state.tasks[attempt.taskId];
263
+ if (task?.task.status !== "ready") {
264
+ throw new DurableTaskRuntimeError(`Task '${attempt.taskId}' is not ready after its dependencies completed.`);
265
+ }
266
+ return;
267
+ }
268
+ if (readiness.reasonCode === "dependencies_incomplete") {
269
+ throw new DurableTaskRuntimeError(`Attempt '${attempt.attemptId}' dependencies are incomplete: ${readiness.dependencyTaskIds.join(", ")}.`);
270
+ }
271
+ if (readiness.reasonCode === "dependency_failed_or_cancelled") {
272
+ throw new DurableTaskRuntimeError(`Attempt '${attempt.attemptId}' has failed or cancelled dependencies: ${readiness.dependencyTaskIds.join(", ")}.`);
273
+ }
274
+ throw new DurableTaskRuntimeError(`Attempt '${attempt.attemptId}' objective is not active (${readiness.objectiveStatus}).`);
275
+ }
276
+ export function activeTaskAttempt(state, task) {
277
+ return task.attemptIds
278
+ .map((attemptId) => state.attempts[attemptId])
279
+ .find((attempt) => attempt !== undefined && !terminalAttemptStatus(attempt.status));
280
+ }
281
+ export function assertTaskAttemptBudgetForState(task, objective) {
282
+ const attemptCeilings = [task.task.riskBudget.maxAttempts, objective.riskBudget.maxAttempts].filter((value) => value !== undefined);
283
+ const maxAttempts = attemptCeilings.length > 0 ? Math.min(...attemptCeilings) : undefined;
284
+ if (maxAttempts !== undefined && task.attemptIds.length >= maxAttempts) {
285
+ throw new DurableTaskRuntimeError(`Task '${task.task.taskId}' exhausted its ${maxAttempts} attempt budget.`);
286
+ }
287
+ }
288
+ function refreshReadyTasks(state, objectiveId, at) {
289
+ const objective = state.objectives[objectiveId];
290
+ if (!objective || objective.objective.status !== "active")
291
+ return;
292
+ const mutableTasks = state.tasks;
293
+ for (const taskId of objective.taskIds) {
294
+ const current = mutableTasks[taskId];
295
+ if (!current || current.task.status !== "pending")
296
+ continue;
297
+ if (taskDependencyReadiness(state, current).state === "ready") {
298
+ mutableTasks[taskId] = { ...current, task: { ...current.task, status: "ready", updatedAt: at } };
299
+ }
300
+ }
301
+ }
302
+ function applyTaskCreated(state, task, occurredAt) {
303
+ const objectives = state.objectives;
304
+ const tasks = state.tasks;
305
+ const objective = objectives[task.objectiveId];
306
+ if (!objective)
307
+ throw new DurableTaskRuntimeError(`Task '${task.taskId}' references an unknown objective.`);
308
+ validateTaskContractForState(state, task, `Task '${task.taskId}'`);
309
+ if (tasks[task.taskId])
310
+ throw new DurableTaskRuntimeError(`Task '${task.taskId}' was created more than once.`);
311
+ assertRecordHasCapacity(tasks, MAX_ORCHESTRATION_TASKS, "task");
312
+ assertIdentifierListHasCapacity(objective.taskIds, MAX_ORCHESTRATION_TASKS, "objective task list");
313
+ tasks[task.taskId] = { task, attemptIds: [] };
314
+ objectives[task.objectiveId] = { ...objective, taskIds: [...objective.taskIds, task.taskId] };
315
+ refreshReadyTasks(state, task.objectiveId, occurredAt);
316
+ }
317
+ function applyAttemptQueued(state, payload, occurredAt) {
318
+ const objectives = state.objectives;
319
+ const tasks = state.tasks;
320
+ const attempts = state.attempts;
321
+ const attemptId = string(payload.attemptId, "attempt.queued.attemptId");
322
+ const taskId = string(payload.taskId, "attempt.queued.taskId");
323
+ const task = tasks[taskId];
324
+ if (!task)
325
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' references an unknown task.`);
326
+ if (attempts[attemptId])
327
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' was queued more than once.`);
328
+ assertRecordHasCapacity(attempts, MAX_ORCHESTRATION_ATTEMPTS, "attempt");
329
+ assertIdentifierListHasCapacity(task.attemptIds, MAX_ORCHESTRATION_ATTEMPTS, "task attempt list");
330
+ const active = activeTaskAttempt(state, task);
331
+ if (active) {
332
+ throw new DurableTaskRuntimeError(`Task '${taskId}' already owns active attempt '${active.attemptId}'.`);
333
+ }
334
+ const objective = objectives[task.task.objectiveId]?.objective;
335
+ if (!objective || objective.status !== "active") {
336
+ throw new DurableTaskRuntimeError(`Objective '${task.task.objectiveId}' is not active.`);
337
+ }
338
+ assertTaskAttemptBudgetForState(task, objective);
339
+ if (!["pending", "ready", "blocked", "failed"].includes(task.task.status)) {
340
+ throw new DurableTaskRuntimeError(`Task '${taskId}' is not dispatchable from '${task.task.status}'.`);
341
+ }
342
+ const dispatch = dispatchFromValue(payload.dispatch, "attempt.queued.dispatch");
343
+ if (dispatch.taskId !== taskId) {
344
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' dispatch task does not match its task.`);
345
+ }
346
+ if (dispatch.executionContract &&
347
+ (dispatch.executionContract.worker.profile.profileId !== dispatch.profileId ||
348
+ dispatch.executionContract.worker.profile.role !== task.task.role)) {
349
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' execution contract does not match its dispatch.`);
350
+ }
351
+ attempts[attemptId] = {
352
+ attemptId,
353
+ taskId,
354
+ dispatch,
355
+ status: "queued",
356
+ ...(typeof payload.grantId === "string" ? { grantId: payload.grantId } : {}),
357
+ checkpointIds: [],
358
+ createdAt: occurredAt,
359
+ updatedAt: occurredAt,
360
+ };
361
+ const nextTask = {
362
+ ...task,
363
+ task: {
364
+ ...task.task,
365
+ status: taskDependencyReadiness(state, task).state === "ready" ? "ready" : "pending",
366
+ updatedAt: occurredAt,
367
+ },
368
+ attemptIds: [...task.attemptIds, attemptId],
369
+ };
370
+ delete nextTask.verification;
371
+ tasks[taskId] = nextTask;
372
+ }
373
+ export function requireActiveObjectiveForAttemptInProjection(state, attempt) {
374
+ const task = state.tasks[attempt.taskId];
375
+ if (!task)
376
+ throw new DurableTaskRuntimeError(`Unknown task '${attempt.taskId}'.`);
377
+ const objective = state.objectives[task.task.objectiveId]?.objective;
378
+ if (!objective)
379
+ throw new DurableTaskRuntimeError(`Unknown objective '${task.task.objectiveId}'.`);
380
+ if (objective.status !== "active") {
381
+ throw new DurableTaskRuntimeError(`Objective '${objective.objectiveId}' is not active.`);
382
+ }
383
+ return objective;
384
+ }
385
+ export function assertRetryBackoffElapsedAt(attempt, atMs) {
386
+ if (!attempt.retry)
387
+ return;
388
+ const remainingMs = Date.parse(attempt.retry.notBefore) - atMs;
389
+ if (remainingMs > 0) {
390
+ throw new DurableTaskRuntimeError(`Attempt '${attempt.attemptId}' retry backoff has ${remainingMs}ms remaining.`);
391
+ }
392
+ }
393
+ function assertLeaseLiveAt(lease, occurredAt, label) {
394
+ const occurredAtMs = Date.parse(occurredAt);
395
+ if (!Number.isFinite(occurredAtMs) || Date.parse(lease.expiresAt) <= occurredAtMs) {
396
+ throw new DurableTaskRuntimeError(`${label} lease has expired.`);
397
+ }
398
+ }
399
+ function assertEventAggregateId(event, expectedId, label) {
400
+ if (event.aggregateId !== expectedId) {
401
+ throw new DurableTaskRuntimeError(`${label} '${expectedId}' does not match aggregate '${event.aggregateId}'.`);
402
+ }
403
+ }
404
+ export function assertAttemptFinishTransition(state, aggregateId, result, occurredAt) {
405
+ if (result.schemaVersion !== ORCHESTRATION_SCHEMA_VERSION ||
406
+ !result.resultId?.trim() ||
407
+ !result.attemptId?.trim() ||
408
+ !result.taskId?.trim() ||
409
+ !result.objectiveId?.trim() ||
410
+ !result.leaseId?.trim() ||
411
+ !WORKER_RESULT_STATUSES.some((status) => status === result.status) ||
412
+ !Array.isArray(result.evidence)) {
413
+ throw new DurableTaskRuntimeError("Worker result contract is invalid.");
414
+ }
415
+ if (aggregateId !== result.attemptId) {
416
+ throw new DurableTaskRuntimeError("Worker result attemptId does not match its aggregate.");
417
+ }
418
+ const attempt = state.attempts[result.attemptId];
419
+ if (!attempt)
420
+ throw new DurableTaskRuntimeError(`Unknown attempt '${result.attemptId}'.`);
421
+ if (attempt.status !== "running" && attempt.status !== "leased") {
422
+ throw new DurableTaskRuntimeError(`Attempt '${result.attemptId}' cannot finish from '${attempt.status}'.`);
423
+ }
424
+ if (!attempt.lease ||
425
+ attempt.lease.leaseId !== result.leaseId ||
426
+ attempt.lease.fencingToken !== result.fencingToken) {
427
+ throw new DurableTaskRuntimeError(`Attempt '${result.attemptId}' lease or fencing token is stale.`);
428
+ }
429
+ assertLeaseLiveAt(attempt.lease, occurredAt, `Attempt '${result.attemptId}'`);
430
+ if (attempt.taskId !== result.taskId) {
431
+ throw new DurableTaskRuntimeError("Worker result taskId does not match attempt.");
432
+ }
433
+ const task = state.tasks[attempt.taskId];
434
+ if (!task || task.task.objectiveId !== result.objectiveId) {
435
+ throw new DurableTaskRuntimeError("Worker result objectiveId does not match attempt.");
436
+ }
437
+ if (result.status === "completed") {
438
+ const missingCriteria = missingTrustedCriteria(result, task.task.acceptanceCriterionIds);
439
+ if (missingCriteria.length > 0) {
440
+ throw new DurableTaskRuntimeError(`Completed result lacks trusted evidence for acceptance criteria: ${missingCriteria.join(", ")}.`);
441
+ }
442
+ }
443
+ return { attempt, task };
444
+ }
445
+ function approvalForAttemptInProjection(state, attemptId) {
446
+ return Object.values(state.approvals).find((approval) => approval.request.attemptId === attemptId);
447
+ }
448
+ export function assertAttemptGrantTransition(state, aggregateId, attemptId, grant) {
449
+ if (aggregateId !== attemptId) {
450
+ throw new DurableTaskRuntimeError(`Grant-bound attempt '${attemptId}' does not match its aggregate.`);
451
+ }
452
+ const attempt = state.attempts[attemptId];
453
+ if (!attempt)
454
+ throw new DurableTaskRuntimeError(`Unknown attempt '${attemptId}'.`);
455
+ if (!["queued", "leased", "running"].includes(attempt.status)) {
456
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' cannot bind a grant from '${attempt.status}'.`);
457
+ }
458
+ const task = state.tasks[attempt.taskId];
459
+ if (!task ||
460
+ grant.schemaVersion !== ORCHESTRATION_SCHEMA_VERSION ||
461
+ !grant.grantId?.trim() ||
462
+ grant.attemptId !== attemptId ||
463
+ grant.taskId !== attempt.taskId ||
464
+ grant.objectiveId !== task.task.objectiveId ||
465
+ grant.role !== task.task.role) {
466
+ throw new DurableTaskRuntimeError("Execution grant target does not match the attempt.");
467
+ }
468
+ const approval = approvalForAttemptInProjection(state, attemptId);
469
+ if (approval?.status === "pending") {
470
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' is awaiting approval '${approval.request.approvalId}'.`);
471
+ }
472
+ if (approval?.status === "rejected") {
473
+ throw new DurableTaskRuntimeError(`Approval '${approval.request.approvalId}' was rejected.`);
474
+ }
475
+ if (attempt.grantId === grant.grantId && attempt.grant && !isDeepStrictEqual(attempt.grant, grant)) {
476
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' grant has conflicting content.`);
477
+ }
478
+ if (attempt.grantId && attempt.grantId !== grant.grantId) {
479
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' already has a different grant.`);
480
+ }
481
+ return attempt;
482
+ }
483
+ export function assertAttemptLeaseTransition(state, aggregateId, lease, agentId, occurredAt) {
484
+ if (aggregateId !== lease.attemptId) {
485
+ throw new DurableTaskRuntimeError(`Leased attempt '${lease.attemptId}' does not match its aggregate.`);
486
+ }
487
+ const attempt = state.attempts[lease.attemptId];
488
+ if (!attempt)
489
+ throw new DurableTaskRuntimeError(`Unknown attempt '${lease.attemptId}'.`);
490
+ if (attempt.status !== "queued")
491
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' is not queued.`);
492
+ assertAttemptDispatchReady(state, attempt);
493
+ const approval = approvalForAttemptInProjection(state, lease.attemptId);
494
+ if (approval?.status === "pending") {
495
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' is awaiting approval '${approval.request.approvalId}'.`);
496
+ }
497
+ if (approval?.status === "rejected") {
498
+ throw new DurableTaskRuntimeError(`Approval '${approval.request.approvalId}' was rejected.`);
499
+ }
500
+ if (!attempt.grantId) {
501
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' requires an execution grant before leasing.`);
502
+ }
503
+ if (lease.fencingToken !== (attempt.lease?.fencingToken ?? 0) + 1) {
504
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' lease fence is not monotonic.`);
505
+ }
506
+ const occurredAtMs = Date.parse(occurredAt);
507
+ const issuedAtMs = Date.parse(lease.issuedAt);
508
+ const expiresAtMs = Date.parse(lease.expiresAt);
509
+ if (!Number.isFinite(occurredAtMs) ||
510
+ !Number.isFinite(issuedAtMs) ||
511
+ !Number.isFinite(expiresAtMs) ||
512
+ issuedAtMs > occurredAtMs ||
513
+ expiresAtMs <= occurredAtMs) {
514
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' lease dates are invalid.`);
515
+ }
516
+ if (agentId) {
517
+ const agent = state.agents[agentId];
518
+ const task = state.tasks[attempt.taskId];
519
+ if (!agent)
520
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
521
+ assertAgentNotRetired(agent, "lease an attempt");
522
+ if (agent.status !== "registered")
523
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' is not idle.`);
524
+ if (!task || task.task.role !== agent.role) {
525
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' role does not match task.`);
526
+ }
527
+ }
528
+ return attempt;
529
+ }
530
+ export function assertAttemptStartTransition(state, aggregateId, attemptId, leaseId, fencingToken, occurredAt) {
531
+ if (aggregateId !== attemptId) {
532
+ throw new DurableTaskRuntimeError(`Started attempt '${attemptId}' does not match its aggregate.`);
533
+ }
534
+ const attempt = state.attempts[attemptId];
535
+ if (!attempt)
536
+ throw new DurableTaskRuntimeError(`Unknown attempt '${attemptId}'.`);
537
+ if (attempt.status !== "leased")
538
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' is not leased.`);
539
+ if (!attempt.lease || attempt.lease.leaseId !== leaseId || attempt.lease.fencingToken !== fencingToken) {
540
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' lease or fencing token is stale.`);
541
+ }
542
+ assertLeaseLiveAt(attempt.lease, occurredAt, `Attempt '${attemptId}'`);
543
+ requireActiveObjectiveForAttemptInProjection(state, attempt);
544
+ return attempt;
545
+ }
546
+ export function assertAttemptCheckpointTransition(state, aggregateId, checkpoint, leaseId, occurredAt) {
547
+ if (aggregateId !== checkpoint.attemptId) {
548
+ throw new DurableTaskRuntimeError(`Checkpointed attempt '${checkpoint.attemptId}' does not match its aggregate.`);
549
+ }
550
+ const attempt = state.attempts[checkpoint.attemptId];
551
+ if (!attempt)
552
+ throw new DurableTaskRuntimeError(`Unknown attempt '${checkpoint.attemptId}'.`);
553
+ if (attempt.status !== "running") {
554
+ throw new DurableTaskRuntimeError(`Attempt '${checkpoint.attemptId}' is not running.`);
555
+ }
556
+ if (!attempt.lease || attempt.lease.leaseId !== leaseId || attempt.lease.fencingToken !== checkpoint.fencingToken) {
557
+ throw new DurableTaskRuntimeError(`Attempt '${checkpoint.attemptId}' lease or fencing token is stale.`);
558
+ }
559
+ assertLeaseLiveAt(attempt.lease, occurredAt, `Attempt '${checkpoint.attemptId}'`);
560
+ return attempt;
561
+ }
562
+ export function assertApprovalRequestTransition(state, aggregateId, approval) {
563
+ const expectedAggregateId = approval.attemptId ?? approval.taskId ?? approval.objectiveId;
564
+ if (aggregateId !== expectedAggregateId) {
565
+ throw new DurableTaskRuntimeError(`Approval target '${expectedAggregateId}' does not match its aggregate.`);
566
+ }
567
+ const objective = state.objectives[approval.objectiveId]?.objective;
568
+ if (!objective)
569
+ throw new DurableTaskRuntimeError(`Unknown objective '${approval.objectiveId}'.`);
570
+ if (objective.status !== "active") {
571
+ throw new DurableTaskRuntimeError(`Objective '${approval.objectiveId}' is not active.`);
572
+ }
573
+ if (approval.attemptId && !approval.taskId) {
574
+ throw new DurableTaskRuntimeError("Attempt-scoped approval requires a taskId.");
575
+ }
576
+ if (approval.taskId) {
577
+ const task = state.tasks[approval.taskId];
578
+ if (!task || task.task.objectiveId !== approval.objectiveId) {
579
+ throw new DurableTaskRuntimeError(`Approval task '${approval.taskId}' does not belong to its objective.`);
580
+ }
581
+ }
582
+ if (approval.attemptId) {
583
+ const attempt = state.attempts[approval.attemptId];
584
+ if (!attempt || attempt.taskId !== approval.taskId) {
585
+ throw new DurableTaskRuntimeError(`Approval attempt '${approval.attemptId}' does not belong to its task.`);
586
+ }
587
+ if (attempt.status !== "queued" || attempt.grantId) {
588
+ throw new DurableTaskRuntimeError(`Approval attempt '${approval.attemptId}' is not awaiting policy.`);
589
+ }
590
+ }
591
+ const pendingForTarget = Object.values(state.approvals).find((candidate) => candidate.status === "pending" &&
592
+ candidate.request.objectiveId === approval.objectiveId &&
593
+ candidate.request.taskId === approval.taskId &&
594
+ candidate.request.attemptId === approval.attemptId);
595
+ if (pendingForTarget) {
596
+ throw new DurableTaskRuntimeError(`Target already awaits approval '${pendingForTarget.request.approvalId}'.`);
597
+ }
598
+ }
599
+ export function assertApprovalResolutionTransition(state, aggregateId, resolution) {
600
+ const approval = state.approvals[resolution.approvalId];
601
+ if (!approval)
602
+ throw new DurableTaskRuntimeError(`Unknown approval '${resolution.approvalId}'.`);
603
+ const expectedAggregateId = approval.request.attemptId ?? approval.request.taskId ?? approval.request.objectiveId;
604
+ if (aggregateId !== expectedAggregateId) {
605
+ throw new DurableTaskRuntimeError(`Resolved approval target '${expectedAggregateId}' does not match aggregate.`);
606
+ }
607
+ if (approval.status !== "pending") {
608
+ throw new DurableTaskRuntimeError(`Approval '${resolution.approvalId}' was already ${approval.status}.`);
609
+ }
610
+ return approval;
611
+ }
612
+ export function assertNotificationTarget(state, objectiveId, attemptId) {
613
+ if (!state.objectives[objectiveId])
614
+ throw new DurableTaskRuntimeError(`Unknown objective '${objectiveId}'.`);
615
+ if (!attemptId)
616
+ return;
617
+ const attempt = state.attempts[attemptId];
618
+ const task = attempt ? state.tasks[attempt.taskId] : undefined;
619
+ if (!attempt || !task || task.task.objectiveId !== objectiveId) {
620
+ throw new DurableTaskRuntimeError(`Notification attempt '${attemptId}' does not belong to its objective.`);
621
+ }
622
+ }
623
+ export function assertTaskFailureTransition(state, aggregateId, taskId, reasonCode) {
624
+ if (aggregateId !== taskId)
625
+ throw new DurableTaskRuntimeError(`Failed task '${taskId}' does not match its aggregate.`);
626
+ const task = state.tasks[taskId];
627
+ if (!task)
628
+ throw new DurableTaskRuntimeError(`Unknown task '${taskId}'.`);
629
+ if (task.task.status !== "pending" && task.task.status !== "ready" && task.task.status !== "blocked") {
630
+ throw new DurableTaskRuntimeError(`Task '${taskId}' cannot fail from '${task.task.status}'.`);
631
+ }
632
+ const active = activeTaskAttempt(state, task);
633
+ if (active) {
634
+ throw new DurableTaskRuntimeError(`Task '${taskId}' still owns active attempt '${active.attemptId}'.`);
635
+ }
636
+ dispatchIdentifier(reasonCode.trim(), "task.failed.reasonCode");
637
+ return task;
638
+ }
639
+ export function assertVerificationTransition(state, aggregateId, args) {
640
+ if (aggregateId !== args.taskId) {
641
+ throw new DurableTaskRuntimeError("Verification subject does not match its aggregate.");
642
+ }
643
+ const subject = state.tasks[args.taskId];
644
+ const verifierTask = state.tasks[args.verifierTaskId];
645
+ const verifierAttempt = state.attempts[args.verifierAttemptId];
646
+ if (!subject)
647
+ throw new DurableTaskRuntimeError(`Unknown verification subject '${args.taskId}'.`);
648
+ if (subject.verification) {
649
+ throw new DurableTaskRuntimeError(`Verification subject '${args.taskId}' was already reconciled.`);
650
+ }
651
+ if (subject.task.status !== "blocked") {
652
+ throw new DurableTaskRuntimeError(`Verification subject '${args.taskId}' cannot reconcile from '${subject.task.status}'.`);
653
+ }
654
+ if (!verifierTask || verifierTask.task.verificationOfTaskId !== args.taskId) {
655
+ throw new DurableTaskRuntimeError(`Task '${args.verifierTaskId}' is not the verifier for '${args.taskId}'.`);
656
+ }
657
+ if (!verifierAttempt || verifierAttempt.taskId !== args.verifierTaskId) {
658
+ throw new DurableTaskRuntimeError(`Unknown verifier attempt '${args.verifierAttemptId}'.`);
659
+ }
660
+ if (!terminalAttemptStatus(verifierAttempt.status)) {
661
+ throw new DurableTaskRuntimeError(`Verifier attempt '${args.verifierAttemptId}' is not terminal.`);
662
+ }
663
+ if (args.verdict === "accepted" && verifierAttempt.status !== "completed") {
664
+ throw new DurableTaskRuntimeError(`Verifier attempt '${args.verifierAttemptId}' is not completed.`);
665
+ }
666
+ dispatchIdentifier(args.reasonCode.trim(), "verification reasonCode");
667
+ if (args.verdict !== "inconclusive" &&
668
+ !verifierAttempt.result?.evidence.some((evidence) => evidence.trusted &&
669
+ evidence.kind === "review" &&
670
+ evidence.metadata?.subjectTaskId === args.taskId &&
671
+ evidence.metadata.verdict === args.verdict)) {
672
+ throw new DurableTaskRuntimeError(`${args.verdict === "accepted" ? "Accepted" : "Rejected"} verification requires matching trusted review evidence for the subject task.`);
673
+ }
674
+ return { subject, verifierAttempt };
675
+ }
676
+ function applyAttemptResumed(state, aggregateId, payload, occurredAt) {
677
+ const attempts = state.attempts;
678
+ const agents = state.agents;
679
+ const lease = leaseFromPayload(payload);
680
+ const agentId = string(payload.agentId, "attempt.resumed.agentId");
681
+ const attempt = attempts[lease.attemptId];
682
+ const agent = agents[agentId];
683
+ if (aggregateId !== lease.attemptId) {
684
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' resume does not match its aggregate.`);
685
+ }
686
+ if (!attempt || attempt.status !== "suspended" || attempt.agentId !== agentId) {
687
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' is not suspended for agent '${agentId}'.`);
688
+ }
689
+ if (!agent || agent.status !== "resuming" || agent.activeAttemptId !== lease.attemptId) {
690
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' is not resuming attempt '${lease.attemptId}'.`);
691
+ }
692
+ assertAgentNotRetired(agent, "resume an attempt");
693
+ if (lease.fencingToken !== (attempt.lease?.fencingToken ?? 0) + 1) {
694
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' resume fence is not monotonic.`);
695
+ }
696
+ const occurredAtMs = Date.parse(occurredAt);
697
+ if (!Number.isFinite(occurredAtMs))
698
+ throw new DurableTaskRuntimeError("Attempt resume event time is invalid.");
699
+ assertRetryBackoffElapsedAt(attempt, occurredAtMs);
700
+ const issuedAtMs = Date.parse(lease.issuedAt);
701
+ const expiresAtMs = Date.parse(lease.expiresAt);
702
+ if (!Number.isFinite(issuedAtMs) ||
703
+ !Number.isFinite(expiresAtMs) ||
704
+ issuedAtMs > occurredAtMs ||
705
+ expiresAtMs <= occurredAtMs) {
706
+ throw new DurableTaskRuntimeError(`Attempt '${lease.attemptId}' resume lease dates are invalid.`);
707
+ }
708
+ requireActiveObjectiveForAttemptInProjection(state, attempt);
709
+ attempts[lease.attemptId] = {
710
+ ...attempt,
711
+ status: "leased",
712
+ agentId,
713
+ lease,
714
+ updatedAt: occurredAt,
715
+ };
716
+ agents[agentId] = {
717
+ ...agent,
718
+ status: "active",
719
+ activeAttemptId: lease.attemptId,
720
+ updatedAt: occurredAt,
721
+ };
722
+ }
723
+ export function reduceOrchestrationEvent(projection, event) {
724
+ assertProjectionWithinLimits(projection);
725
+ if (event.ordinal <= projection.lastOrdinal)
726
+ return projection;
727
+ if (event.ordinal !== projection.lastOrdinal + 1) {
728
+ throw new DurableTaskRuntimeError(`Orchestration event ordinal ${event.ordinal} is not contiguous after ${projection.lastOrdinal}.`);
729
+ }
730
+ const state = cloneProjection(projection);
731
+ const byteTracker = new ProjectionByteTracker(projection);
732
+ const rawAgents = state.agents;
733
+ const rawObjectives = state.objectives;
734
+ const rawTasks = state.tasks;
735
+ const rawAttempts = state.attempts;
736
+ const rawCheckpoints = state.checkpoints;
737
+ const rawApprovals = state.approvals;
738
+ const rawNotifications = state.notifications;
739
+ const agents = byteTracker.track("agents", rawAgents);
740
+ const objectives = byteTracker.track("objectives", rawObjectives);
741
+ const tasks = byteTracker.track("tasks", rawTasks);
742
+ const attempts = byteTracker.track("attempts", rawAttempts);
743
+ const checkpoints = byteTracker.track("checkpoints", rawCheckpoints);
744
+ const approvals = byteTracker.track("approvals", rawApprovals);
745
+ const notifications = byteTracker.track("notifications", rawNotifications);
746
+ state.agents = agents;
747
+ state.objectives = objectives;
748
+ state.tasks = tasks;
749
+ state.attempts = attempts;
750
+ state.checkpoints = checkpoints;
751
+ state.approvals = approvals;
752
+ state.notifications = notifications;
753
+ switch (event.type) {
754
+ case "objective.created": {
755
+ const objective = objectiveFromPayload(event.payload);
756
+ assertEventAggregateId(event, objective.objectiveId, "Created objective");
757
+ if (objective.status !== "active") {
758
+ throw new DurableTaskRuntimeError(`Created objective '${objective.objectiveId}' must be active.`);
759
+ }
760
+ if (objectives[objective.objectiveId]) {
761
+ throw new DurableTaskRuntimeError(`Objective '${objective.objectiveId}' was created more than once.`);
762
+ }
763
+ assertRecordHasCapacity(objectives, MAX_ORCHESTRATION_OBJECTIVES, "objective");
764
+ objectives[objective.objectiveId] = { objective, taskIds: [], evidence: [] };
765
+ break;
766
+ }
767
+ case "objective.updated": {
768
+ const objective = objectiveFromPayload(event.payload);
769
+ const current = assertObjectiveUpdateTransition(state, event.aggregateId, objective);
770
+ objectives[event.aggregateId] = { ...current, objective };
771
+ break;
772
+ }
773
+ case "objective.evidence_recorded": {
774
+ const evidence = evidenceFromPayload(event.payload);
775
+ const current = assertObjectiveEvidenceTransition(state, event.aggregateId, evidence);
776
+ const existing = current.evidence.find((candidate) => candidate.evidenceId === evidence.evidenceId);
777
+ if (existing && !isDeepStrictEqual(existing, evidence)) {
778
+ throw new DurableTaskRuntimeError(`Objective evidence '${evidence.evidenceId}' has conflicting content.`);
779
+ }
780
+ if (!existing) {
781
+ assertObjectiveEvidenceHasCapacity(objectives, current);
782
+ objectives[event.aggregateId] = { ...current, evidence: [...current.evidence, evidence] };
783
+ }
784
+ break;
785
+ }
786
+ case "objective.paused":
787
+ assertObjectiveStatusTransition(state, event.aggregateId, "paused");
788
+ updateObjectiveStatus(state, event.aggregateId, "paused", event.occurredAt);
789
+ break;
790
+ case "objective.resumed":
791
+ assertObjectiveStatusTransition(state, event.aggregateId, "active");
792
+ updateObjectiveStatus(state, event.aggregateId, "active", event.occurredAt);
793
+ refreshReadyTasks(state, event.aggregateId, event.occurredAt);
794
+ break;
795
+ case "objective.completed": {
796
+ const policy = string(event.payload.completionPolicy, "objective.completed.completionPolicy");
797
+ if (policy !== "task_evidence" && policy !== "owner_evidence" && policy !== "owner_override") {
798
+ throw new DurableTaskRuntimeError(`Unknown objective completion policy '${policy}'.`);
799
+ }
800
+ assertObjectiveStatusTransition(state, event.aggregateId, "completed", policy);
801
+ updateObjectiveStatus(state, event.aggregateId, "completed", event.occurredAt);
802
+ cancelOpenObjectiveWork(state, event.aggregateId, event.occurredAt, "objective_completed");
803
+ break;
804
+ }
805
+ case "objective.cancelled": {
806
+ assertObjectiveStatusTransition(state, event.aggregateId, "cancelled");
807
+ updateObjectiveStatus(state, event.aggregateId, "cancelled", event.occurredAt);
808
+ cancelOpenObjectiveWork(state, event.aggregateId, event.occurredAt, "objective_cancelled");
809
+ break;
810
+ }
811
+ case "task.created": {
812
+ const task = taskFromPayload(event.payload);
813
+ assertEventAggregateId(event, task.objectiveId, "Created task objective");
814
+ applyTaskCreated(state, task, event.occurredAt);
815
+ break;
816
+ }
817
+ case "task.attempt_prepared": {
818
+ const task = taskFromPayload(event.payload);
819
+ if (event.aggregateId !== task.taskId) {
820
+ throw new DurableTaskRuntimeError(`Prepared task '${task.taskId}' does not match its aggregate.`);
821
+ }
822
+ applyTaskCreated(state, task, event.occurredAt);
823
+ applyAttemptQueued(state, event.payload, event.occurredAt);
824
+ break;
825
+ }
826
+ case "task.ready": {
827
+ const taskId = string(event.payload.taskId, "task.ready.taskId");
828
+ assertEventAggregateId(event, taskId, "Ready task");
829
+ const current = tasks[taskId];
830
+ if (!current)
831
+ throw new DurableTaskRuntimeError(`Unknown task '${taskId}'.`);
832
+ if (current.task.status !== "pending") {
833
+ throw new DurableTaskRuntimeError(`Task '${taskId}' cannot become ready from '${current.task.status}'.`);
834
+ }
835
+ if (taskDependencyReadiness(state, current).state !== "ready") {
836
+ throw new DurableTaskRuntimeError(`Task '${taskId}' dependencies are incomplete.`);
837
+ }
838
+ tasks[taskId] = { ...current, task: { ...current.task, status: "ready", updatedAt: event.occurredAt } };
839
+ break;
840
+ }
841
+ case "task.failed": {
842
+ const taskId = string(event.payload.taskId, "task.failed.taskId");
843
+ const reasonCode = string(event.payload.reasonCode, "task.failed.reasonCode");
844
+ const current = assertTaskFailureTransition(state, event.aggregateId, taskId, reasonCode);
845
+ tasks[taskId] = { ...current, task: { ...current.task, status: "failed", updatedAt: event.occurredAt } };
846
+ break;
847
+ }
848
+ case "task.verification_finished": {
849
+ const taskId = string(event.payload.taskId, "task.verification_finished.taskId");
850
+ const verifierTaskId = string(event.payload.verifierTaskId, "task.verification_finished.verifierTaskId");
851
+ const verifierAttemptId = string(event.payload.verifierAttemptId, "task.verification_finished.verifierAttemptId");
852
+ const verdict = string(event.payload.verdict, "task.verification_finished.verdict");
853
+ if (verdict !== "accepted" && verdict !== "rejected" && verdict !== "inconclusive") {
854
+ throw new DurableTaskRuntimeError(`Unknown verification verdict '${verdict}'.`);
855
+ }
856
+ const reasonCode = string(event.payload.reasonCode, "task.verification_finished.reasonCode");
857
+ const transition = {
858
+ taskId,
859
+ verifierTaskId,
860
+ verifierAttemptId,
861
+ verdict,
862
+ reasonCode,
863
+ };
864
+ const { subject: current } = assertVerificationTransition(state, event.aggregateId, transition);
865
+ tasks[taskId] = {
866
+ ...current,
867
+ task: {
868
+ ...current.task,
869
+ status: verdict === "accepted" ? "completed" : "blocked",
870
+ updatedAt: event.occurredAt,
871
+ },
872
+ verification: {
873
+ verifierTaskId,
874
+ verifierAttemptId,
875
+ verdict,
876
+ reasonCode,
877
+ completedAt: event.occurredAt,
878
+ },
879
+ };
880
+ refreshReadyTasks(state, current.task.objectiveId, event.occurredAt);
881
+ break;
882
+ }
883
+ case "agent.registered": {
884
+ const agent = agentFromPayload(event.payload);
885
+ assertEventAggregateId(event, agent.agentId, "Registered agent");
886
+ if (agent.status !== "registered" || agent.activeAttemptId) {
887
+ throw new DurableTaskRuntimeError(`Registered agent '${agent.agentId}' must start idle.`);
888
+ }
889
+ if (agents[agent.agentId])
890
+ throw new DurableTaskRuntimeError(`Agent '${agent.agentId}' was registered more than once.`);
891
+ assertRecordHasCapacity(agents, MAX_ORCHESTRATION_AGENT_BINDINGS, "agent binding");
892
+ if (agent.parentAgentId) {
893
+ const parent = agents[agent.parentAgentId];
894
+ if (!parent)
895
+ throw new DurableTaskRuntimeError(`Unknown parent agent '${agent.parentAgentId}'.`);
896
+ if (parent.status === "retired") {
897
+ throw new DurableTaskRuntimeError(`Parent agent '${agent.parentAgentId}' is retired.`);
898
+ }
899
+ if (agent.rootAgentId !== parent.rootAgentId || agent.depth !== parent.depth + 1) {
900
+ throw new DurableTaskRuntimeError(`Agent '${agent.agentId}' lineage conflicts with its parent.`);
901
+ }
902
+ }
903
+ else if (agent.rootAgentId !== agent.agentId || agent.depth !== 0) {
904
+ throw new DurableTaskRuntimeError(`Root agent '${agent.agentId}' lineage is invalid.`);
905
+ }
906
+ agents[agent.agentId] = agent;
907
+ break;
908
+ }
909
+ case "agent.retired": {
910
+ const agentId = string(event.payload.agentId, "agent.retired.agentId");
911
+ if (event.aggregateId !== agentId) {
912
+ throw new DurableTaskRuntimeError(`Retired agent '${agentId}' does not match its aggregate.`);
913
+ }
914
+ const agent = assertAgentRetirementEligible(state, agentId);
915
+ if (agent.status === "retired")
916
+ break;
917
+ agents[agentId] = { ...agent, status: "retired", updatedAt: event.occurredAt };
918
+ break;
919
+ }
920
+ case "agent.suspended": {
921
+ const agentId = string(event.payload.agentId, "agent.suspended.agentId");
922
+ assertEventAggregateId(event, agentId, "Suspended agent");
923
+ const agent = agents[agentId];
924
+ if (!agent)
925
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
926
+ assertAgentNotRetired(agent, "suspend");
927
+ throw new DurableTaskRuntimeError("Standalone agent suspension is unsupported; suspend the bound attempt atomically.");
928
+ }
929
+ case "agent.resume_requested": {
930
+ const agentId = string(event.payload.agentId, "agent.resume_requested.agentId");
931
+ const attemptId = string(event.payload.attemptId, "agent.resume_requested.attemptId");
932
+ if (event.aggregateId !== agentId) {
933
+ throw new DurableTaskRuntimeError(`Resuming agent '${agentId}' does not match its aggregate.`);
934
+ }
935
+ const agent = agents[agentId];
936
+ if (!agent)
937
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
938
+ assertAgentNotRetired(agent, "request resume");
939
+ const attempt = attempts[attemptId];
940
+ if (!attempt ||
941
+ attempt.status !== "suspended" ||
942
+ attempt.agentId !== agentId ||
943
+ agent.activeAttemptId !== attemptId) {
944
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' cannot resume suspended attempt '${attemptId}'.`);
945
+ }
946
+ if (agent.status === "resuming")
947
+ break;
948
+ if (agent.status !== "suspended") {
949
+ throw new DurableTaskRuntimeError(`Agent '${agentId}' is not suspended.`);
950
+ }
951
+ const occurredAtMs = Date.parse(event.occurredAt);
952
+ if (!Number.isFinite(occurredAtMs)) {
953
+ throw new DurableTaskRuntimeError("Agent resume request event time is invalid.");
954
+ }
955
+ assertRetryBackoffElapsedAt(attempt, occurredAtMs);
956
+ requireActiveObjectiveForAttemptInProjection(state, attempt);
957
+ agents[agentId] = { ...agent, status: "resuming", updatedAt: event.occurredAt };
958
+ break;
959
+ }
960
+ case "agent.resumed": {
961
+ const agentId = string(event.payload.agentId, "agent.resumed.agentId");
962
+ assertEventAggregateId(event, agentId, "Resumed agent");
963
+ const agent = agents[agentId];
964
+ if (!agent)
965
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
966
+ assertAgentNotRetired(agent, "resume");
967
+ throw new DurableTaskRuntimeError("Standalone agent resume is unsupported; resume the suspended attempt atomically.");
968
+ }
969
+ case "attempt.queued": {
970
+ assertEventAggregateId(event, string(event.payload.taskId, "attempt.queued.taskId"), "Queued attempt task");
971
+ applyAttemptQueued(state, event.payload, event.occurredAt);
972
+ break;
973
+ }
974
+ case "attempt.grant_bound": {
975
+ const attemptId = string(event.payload.attemptId, "attempt.grant_bound.attemptId");
976
+ const grant = executionGrantFromValue(event.payload.grant, "attempt.grant_bound.grant");
977
+ const attempt = assertAttemptGrantTransition(state, event.aggregateId, attemptId, grant);
978
+ attempts[attemptId] = {
979
+ ...attempt,
980
+ grantId: grant.grantId,
981
+ grant,
982
+ updatedAt: event.occurredAt,
983
+ };
984
+ break;
985
+ }
986
+ case "attempt.leased": {
987
+ const lease = leaseFromPayload(event.payload);
988
+ const agentId = typeof event.payload.agentId === "string" ? event.payload.agentId : undefined;
989
+ const attempt = assertAttemptLeaseTransition(state, event.aggregateId, lease, agentId, event.occurredAt);
990
+ if (agentId) {
991
+ const agent = agents[agentId];
992
+ if (!agent)
993
+ throw new DurableTaskRuntimeError(`Unknown agent '${agentId}'.`);
994
+ agents[agentId] = {
995
+ ...agent,
996
+ status: "active",
997
+ activeAttemptId: lease.attemptId,
998
+ updatedAt: event.occurredAt,
999
+ };
1000
+ }
1001
+ attempts[lease.attemptId] = {
1002
+ ...attempt,
1003
+ status: "leased",
1004
+ lease,
1005
+ ...(agentId ? { agentId } : {}),
1006
+ updatedAt: event.occurredAt,
1007
+ };
1008
+ const task = tasks[attempt.taskId];
1009
+ if (!task)
1010
+ throw new DurableTaskRuntimeError(`Unknown task '${attempt.taskId}'.`);
1011
+ tasks[attempt.taskId] = {
1012
+ ...task,
1013
+ task: { ...task.task, status: "running", updatedAt: event.occurredAt },
1014
+ };
1015
+ break;
1016
+ }
1017
+ case "attempt.started": {
1018
+ const attemptId = string(event.payload.attemptId, "attempt.started.attemptId");
1019
+ const leaseId = string(event.payload.leaseId, "attempt.started.leaseId");
1020
+ const fencingToken = number(event.payload.fencingToken, "attempt.started.fencingToken");
1021
+ const attempt = assertAttemptStartTransition(state, event.aggregateId, attemptId, leaseId, fencingToken, event.occurredAt);
1022
+ attempts[attemptId] = { ...attempt, status: "running", updatedAt: event.occurredAt };
1023
+ break;
1024
+ }
1025
+ case "attempt.checkpointed": {
1026
+ const checkpoint = checkpointFromPayload(event.payload);
1027
+ const leaseId = string(event.payload.leaseId, "attempt.checkpointed.leaseId");
1028
+ const attempt = assertAttemptCheckpointTransition(state, event.aggregateId, checkpoint, leaseId, event.occurredAt);
1029
+ if (checkpoints[checkpoint.checkpointId]) {
1030
+ throw new DurableTaskRuntimeError(`Checkpoint '${checkpoint.checkpointId}' was recorded more than once.`);
1031
+ }
1032
+ assertIdentifierListHasCapacity(attempt.checkpointIds, MAX_ORCHESTRATION_CHECKPOINTS, "attempt checkpoint list");
1033
+ assertRecordHasCapacity(checkpoints, MAX_ORCHESTRATION_CHECKPOINTS, "checkpoint");
1034
+ checkpoints[checkpoint.checkpointId] = checkpoint;
1035
+ attempts[checkpoint.attemptId] = {
1036
+ ...attempt,
1037
+ checkpointIds: [...attempt.checkpointIds, checkpoint.checkpointId],
1038
+ updatedAt: event.occurredAt,
1039
+ };
1040
+ if (attempt.agentId) {
1041
+ const agent = agents[attempt.agentId];
1042
+ if (agent) {
1043
+ assertAgentNotRetired(agent, "checkpoint an attempt");
1044
+ agents[attempt.agentId] = {
1045
+ ...agent,
1046
+ resumeContext: { ...agent.resumeContext, latestCheckpointId: checkpoint.checkpointId },
1047
+ updatedAt: event.occurredAt,
1048
+ };
1049
+ }
1050
+ }
1051
+ break;
1052
+ }
1053
+ case "attempt.suspended": {
1054
+ const attemptId = string(event.payload.attemptId, "attempt.suspended.attemptId");
1055
+ assertEventAggregateId(event, attemptId, "Suspended attempt");
1056
+ const attempt = attempts[attemptId];
1057
+ if (!attempt)
1058
+ throw new DurableTaskRuntimeError(`Unknown attempt '${attemptId}'.`);
1059
+ const leaseId = string(event.payload.leaseId, "attempt.suspended.leaseId");
1060
+ const fencingToken = number(event.payload.fencingToken, "attempt.suspended.fencingToken");
1061
+ if (!attempt.lease || attempt.lease.leaseId !== leaseId || attempt.lease.fencingToken !== fencingToken) {
1062
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' lease or fencing token is stale.`);
1063
+ }
1064
+ if (attempt.status !== "leased" && attempt.status !== "running") {
1065
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' cannot suspend from '${attempt.status}'.`);
1066
+ }
1067
+ if (!attempt.agentId) {
1068
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' is not bound to a resumable agent.`);
1069
+ }
1070
+ const retry = event.payload.retry === undefined
1071
+ ? undefined
1072
+ : retryStateFromValue(event.payload.retry, "attempt.suspended.retry");
1073
+ if (retry && retry.retriesUsed !== (attempt.retry?.retriesUsed ?? 0) + 1) {
1074
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' retry count is not monotonic.`);
1075
+ }
1076
+ if (retry && Date.parse(retry.notBefore) <= Date.parse(event.occurredAt)) {
1077
+ throw new DurableTaskRuntimeError("Retry not-before deadline must be after the suspension event.");
1078
+ }
1079
+ attempts[attemptId] = {
1080
+ ...attempt,
1081
+ status: "suspended",
1082
+ ...(typeof event.payload.reasonCode === "string" ? { reasonCode: event.payload.reasonCode } : {}),
1083
+ ...(retry ? { retry } : {}),
1084
+ updatedAt: event.occurredAt,
1085
+ };
1086
+ if (attempt.agentId) {
1087
+ const agent = agents[attempt.agentId];
1088
+ if (agent) {
1089
+ assertAgentNotRetired(agent, "suspend an attempt");
1090
+ agents[attempt.agentId] = { ...agent, status: "suspended", updatedAt: event.occurredAt };
1091
+ }
1092
+ }
1093
+ break;
1094
+ }
1095
+ case "attempt.resumed": {
1096
+ applyAttemptResumed(state, event.aggregateId, event.payload, event.occurredAt);
1097
+ break;
1098
+ }
1099
+ case "attempt.cancelled": {
1100
+ const attemptId = string(event.payload.attemptId, "attempt.cancelled.attemptId");
1101
+ assertEventAggregateId(event, attemptId, "Cancelled attempt");
1102
+ const attempt = attempts[attemptId];
1103
+ if (!attempt)
1104
+ throw new DurableTaskRuntimeError(`Unknown attempt '${attemptId}'.`);
1105
+ if (terminalAttemptStatus(attempt.status)) {
1106
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' cannot cancel from '${attempt.status}'.`);
1107
+ }
1108
+ attempts[attemptId] = withoutAttemptRetry({
1109
+ ...attempt,
1110
+ status: "cancelled",
1111
+ reasonCode: string(event.payload.reasonCode, "attempt.cancelled.reasonCode"),
1112
+ updatedAt: event.occurredAt,
1113
+ });
1114
+ const task = tasks[attempt.taskId];
1115
+ if (task) {
1116
+ tasks[attempt.taskId] = {
1117
+ ...task,
1118
+ task: { ...task.task, status: "cancelled", updatedAt: event.occurredAt },
1119
+ };
1120
+ }
1121
+ releaseAttemptAgent(agents, attempt, event.occurredAt);
1122
+ break;
1123
+ }
1124
+ case "attempt.finished": {
1125
+ const result = resultFromPayload(event.payload);
1126
+ const { attempt, task } = assertAttemptFinishTransition(state, event.aggregateId, result, event.occurredAt);
1127
+ attempts[result.attemptId] = withoutAttemptRetry({
1128
+ ...attempt,
1129
+ status: result.status,
1130
+ result,
1131
+ updatedAt: event.occurredAt,
1132
+ });
1133
+ tasks[result.taskId] = {
1134
+ ...task,
1135
+ task: { ...task.task, status: taskStatusForResult(result.status), updatedAt: event.occurredAt },
1136
+ };
1137
+ releaseAttemptAgent(agents, attempt, event.occurredAt);
1138
+ refreshReadyTasks(state, task.task.objectiveId, event.occurredAt);
1139
+ break;
1140
+ }
1141
+ case "attempt.lease_expired": {
1142
+ const attemptId = string(event.payload.attemptId, "attempt.lease_expired.attemptId");
1143
+ assertEventAggregateId(event, attemptId, "Expired attempt");
1144
+ const attempt = attempts[attemptId];
1145
+ if (!attempt)
1146
+ throw new DurableTaskRuntimeError(`Unknown attempt '${attemptId}'.`);
1147
+ if ((attempt.status !== "leased" && attempt.status !== "running") || !attempt.lease || attempt.agentId) {
1148
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' cannot expire from '${attempt.status}'.`);
1149
+ }
1150
+ const leaseId = string(event.payload.leaseId, "attempt.lease_expired.leaseId");
1151
+ const fencingToken = number(event.payload.fencingToken, "attempt.lease_expired.fencingToken");
1152
+ if (attempt.lease.leaseId !== leaseId || attempt.lease.fencingToken !== fencingToken) {
1153
+ throw new DurableTaskRuntimeError(`Attempt '${attemptId}' lease or fencing token is stale.`);
1154
+ }
1155
+ attempts[attemptId] = withoutAttemptRetry({
1156
+ ...attempt,
1157
+ status: "expired",
1158
+ ...(typeof event.payload.reasonCode === "string" ? { reasonCode: event.payload.reasonCode } : {}),
1159
+ updatedAt: event.occurredAt,
1160
+ });
1161
+ const task = tasks[attempt.taskId];
1162
+ if (task)
1163
+ tasks[attempt.taskId] = { ...task, task: { ...task.task, status: "ready", updatedAt: event.occurredAt } };
1164
+ break;
1165
+ }
1166
+ case "approval.requested": {
1167
+ const approval = approvalFromPayload(event.payload);
1168
+ assertApprovalRequestTransition(state, event.aggregateId, approval);
1169
+ if (approvals[approval.approvalId]) {
1170
+ throw new DurableTaskRuntimeError(`Approval '${approval.approvalId}' was requested more than once.`);
1171
+ }
1172
+ const notificationId = `approval-requested:${approval.approvalId}`;
1173
+ if (notifications[notificationId]) {
1174
+ throw new DurableTaskRuntimeError(`Notification '${notificationId}' already exists.`);
1175
+ }
1176
+ assertRecordHasCapacity(approvals, MAX_ORCHESTRATION_APPROVALS, "approval");
1177
+ assertRecordHasCapacity(notifications, MAX_ORCHESTRATION_NOTIFICATIONS, "notification");
1178
+ approvals[approval.approvalId] = { request: approval, status: "pending" };
1179
+ notifications[notificationId] = {
1180
+ notificationId,
1181
+ objectiveId: approval.objectiveId,
1182
+ ...(approval.attemptId ? { attemptId: approval.attemptId } : {}),
1183
+ status: "pending",
1184
+ message: approval.summary,
1185
+ createdAt: event.occurredAt,
1186
+ };
1187
+ break;
1188
+ }
1189
+ case "approval.resolved": {
1190
+ const resolution = approvalResolutionFromPayload(event.payload);
1191
+ const approval = assertApprovalResolutionTransition(state, event.aggregateId, resolution);
1192
+ approvals[resolution.approvalId] = {
1193
+ ...approval,
1194
+ status: resolution.outcome,
1195
+ resolution,
1196
+ };
1197
+ const approvalNotification = notifications[`approval-requested:${resolution.approvalId}`];
1198
+ if (approvalNotification?.status === "pending") {
1199
+ notifications[approvalNotification.notificationId] = {
1200
+ ...approvalNotification,
1201
+ status: "delivered",
1202
+ deliveredAt: event.occurredAt,
1203
+ };
1204
+ }
1205
+ if (resolution.outcome === "rejected" && approval.request.attemptId) {
1206
+ const attempt = attempts[approval.request.attemptId];
1207
+ if (attempt && !terminalAttemptStatus(attempt.status)) {
1208
+ attempts[attempt.attemptId] = withoutAttemptRetry({
1209
+ ...attempt,
1210
+ status: "blocked",
1211
+ reasonCode: "approval_rejected",
1212
+ updatedAt: event.occurredAt,
1213
+ });
1214
+ const task = tasks[attempt.taskId];
1215
+ if (task) {
1216
+ tasks[attempt.taskId] = {
1217
+ ...task,
1218
+ task: { ...task.task, status: "blocked", updatedAt: event.occurredAt },
1219
+ };
1220
+ }
1221
+ }
1222
+ }
1223
+ break;
1224
+ }
1225
+ case "notification.enqueued": {
1226
+ const notificationId = string(event.payload.notificationId, "notification.enqueued.notificationId");
1227
+ const objectiveId = string(event.payload.objectiveId, "notification.enqueued.objectiveId");
1228
+ const attemptId = typeof event.payload.attemptId === "string" ? event.payload.attemptId : undefined;
1229
+ assertEventAggregateId(event, objectiveId, "Notification objective");
1230
+ assertNotificationTarget(state, objectiveId, attemptId);
1231
+ if (notifications[notificationId]) {
1232
+ throw new DurableTaskRuntimeError(`Notification '${notificationId}' was enqueued more than once.`);
1233
+ }
1234
+ assertRecordHasCapacity(notifications, MAX_ORCHESTRATION_NOTIFICATIONS, "notification");
1235
+ notifications[notificationId] = {
1236
+ notificationId,
1237
+ objectiveId,
1238
+ ...(attemptId ? { attemptId } : {}),
1239
+ status: "pending",
1240
+ message: string(event.payload.message, "notification.enqueued.message"),
1241
+ createdAt: event.occurredAt,
1242
+ };
1243
+ break;
1244
+ }
1245
+ case "notification.delivered": {
1246
+ const notificationId = string(event.payload.notificationId, "notification.delivered.notificationId");
1247
+ const notification = notifications[notificationId];
1248
+ if (!notification)
1249
+ throw new DurableTaskRuntimeError(`Unknown notification '${notificationId}'.`);
1250
+ assertEventAggregateId(event, notification.objectiveId, "Delivered notification objective");
1251
+ notifications[notificationId] = { ...notification, status: "delivered", deliveredAt: event.occurredAt };
1252
+ break;
1253
+ }
1254
+ }
1255
+ const serializedBytes = byteTracker.finish(event.ordinal);
1256
+ state.agents = rawAgents;
1257
+ state.objectives = rawObjectives;
1258
+ state.tasks = rawTasks;
1259
+ state.attempts = rawAttempts;
1260
+ state.checkpoints = rawCheckpoints;
1261
+ state.approvals = rawApprovals;
1262
+ state.notifications = rawNotifications;
1263
+ const result = { ...state, lastOrdinal: event.ordinal };
1264
+ cacheProjectionSerializedBytes(result, serializedBytes);
1265
+ return result;
1266
+ }
1267
+ export function projectOrchestrationEvents(events) {
1268
+ return events.reduce(reduceOrchestrationEvent, emptyProjection());
1269
+ }
1270
+ //# sourceMappingURL=task-runtime-reducer.js.map