@osolmaz/pi-workflows 0.16.1 → 0.16.3

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 (327) hide show
  1. package/README.md +41 -41
  2. package/dist/builtins/monitor.workflow.d.ts +1 -1
  3. package/dist/builtins/monitor.workflow.js +8 -8
  4. package/dist/builtins/pi-agent-group.js +3 -3
  5. package/dist/builtins/pi-agent-group.js.map +1 -1
  6. package/dist/channels/adapter-entry.js +14 -14
  7. package/dist/channels/adapter-entry.js.map +1 -1
  8. package/dist/client/client.d.ts +6 -6
  9. package/dist/client/client.js +27 -27
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/protocol.d.ts +4 -1
  12. package/dist/client/protocol.js +17 -11
  13. package/dist/client/protocol.js.map +1 -1
  14. package/dist/client/resolver.d.ts +4 -4
  15. package/dist/client/view.d.ts +7 -1
  16. package/dist/client/view.js +1 -0
  17. package/dist/client/view.js.map +1 -1
  18. package/dist/extension/index.d.ts +1 -1
  19. package/dist/extension/index.js +66 -47
  20. package/dist/extension/index.js.map +1 -1
  21. package/dist/extension/recorder.d.ts +1 -1
  22. package/dist/extension/recorder.js +1 -1
  23. package/dist/extension/recorder.js.map +1 -1
  24. package/dist/extension/remote-recorder-store.d.ts +1 -1
  25. package/dist/extension/remote-recorder-store.js +2 -2
  26. package/dist/extension/remote-recorder-store.js.map +1 -1
  27. package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
  28. package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
  29. package/dist/extension/resource-manager-command.js.map +1 -0
  30. package/dist/extension/session-delivery.d.ts +1 -1
  31. package/dist/extension/session-delivery.js +2 -2
  32. package/dist/extension/session-delivery.js.map +1 -1
  33. package/dist/extension/session-view.d.ts +1 -1
  34. package/dist/extension/session-view.js +1 -1
  35. package/dist/extension/session-view.js.map +1 -1
  36. package/dist/extension/workflow-message-coordinator.d.ts +4 -3
  37. package/dist/extension/workflow-message-coordinator.js +81 -29
  38. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  39. package/dist/resource-managers/conditions.d.ts +6 -0
  40. package/dist/{controllers → resource-managers}/conditions.js +3 -3
  41. package/dist/resource-managers/conditions.js.map +1 -0
  42. package/dist/resource-managers/definition.d.ts +6 -0
  43. package/dist/resource-managers/definition.js +45 -0
  44. package/dist/resource-managers/definition.js.map +1 -0
  45. package/dist/resource-managers/effects.d.ts +15 -0
  46. package/dist/{controllers → resource-managers}/effects.js +2 -2
  47. package/dist/resource-managers/effects.js.map +1 -0
  48. package/dist/resource-managers/errors.d.ts +12 -0
  49. package/dist/resource-managers/errors.js +25 -0
  50. package/dist/resource-managers/errors.js.map +1 -0
  51. package/dist/resource-managers/index.d.ts +12 -0
  52. package/dist/resource-managers/index.js +12 -0
  53. package/dist/resource-managers/index.js.map +1 -0
  54. package/dist/resource-managers/json.js.map +1 -0
  55. package/dist/resource-managers/loader.d.ts +23 -0
  56. package/dist/resource-managers/loader.js +75 -0
  57. package/dist/resource-managers/loader.js.map +1 -0
  58. package/dist/resource-managers/results.d.ts +5 -0
  59. package/dist/resource-managers/results.js.map +1 -0
  60. package/dist/resource-managers/runtime.d.ts +59 -0
  61. package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
  62. package/dist/resource-managers/runtime.js.map +1 -0
  63. package/dist/{controllers → resource-managers}/sqlite.d.ts +51 -43
  64. package/dist/{controllers → resource-managers}/sqlite.js +187 -125
  65. package/dist/resource-managers/sqlite.js.map +1 -0
  66. package/dist/{controllers → resource-managers}/store.d.ts +33 -33
  67. package/dist/{controllers → resource-managers}/store.js.map +1 -1
  68. package/dist/{controllers → resource-managers}/types.d.ts +41 -41
  69. package/dist/{controllers → resource-managers}/types.js.map +1 -1
  70. package/dist/resource-managers/workflows.d.ts +34 -0
  71. package/dist/{controllers → resource-managers}/workflows.js +15 -15
  72. package/dist/resource-managers/workflows.js.map +1 -0
  73. package/dist/server/channel-effects.js.map +1 -0
  74. package/dist/{host → server}/channel-supervisor.d.ts +6 -6
  75. package/dist/{host → server}/channel-supervisor.js +3 -3
  76. package/dist/server/channel-supervisor.js.map +1 -0
  77. package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
  78. package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
  79. package/dist/server/child-runner-supervisor.js.map +1 -0
  80. package/dist/{host → server}/processes.d.ts +3 -3
  81. package/dist/{host → server}/processes.js +3 -3
  82. package/dist/server/processes.js.map +1 -0
  83. package/dist/{host → server}/resolver-entry.d.ts +6 -6
  84. package/dist/{host → server}/resolver-entry.js +14 -14
  85. package/dist/server/resolver-entry.js.map +1 -0
  86. package/dist/server/resource-runner-entry.d.ts +2 -0
  87. package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
  88. package/dist/server/resource-runner-entry.js.map +1 -0
  89. package/dist/server/resource-runner-protocol.d.ts +36 -0
  90. package/dist/server/resource-runner-protocol.js +49 -0
  91. package/dist/server/resource-runner-protocol.js.map +1 -0
  92. package/dist/server/resource-runner-supervisor.d.ts +21 -0
  93. package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
  94. package/dist/server/resource-runner-supervisor.js.map +1 -0
  95. package/dist/{host → server}/rpc-bridge.d.ts +3 -3
  96. package/dist/{host → server}/rpc-bridge.js +3 -3
  97. package/dist/server/rpc-bridge.js.map +1 -0
  98. package/dist/{host → server}/rpc-executor.d.ts +1 -1
  99. package/dist/{host → server}/rpc-executor.js +3 -3
  100. package/dist/server/rpc-executor.js.map +1 -0
  101. package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
  102. package/dist/server/server-entry.js.map +1 -0
  103. package/dist/{host/runner.d.ts → server/server.d.ts} +47 -39
  104. package/dist/{host/runner.js → server/server.js} +624 -535
  105. package/dist/server/server.js.map +1 -0
  106. package/dist/{host → server}/state.d.ts +25 -25
  107. package/dist/{host → server}/state.js +49 -49
  108. package/dist/server/state.js.map +1 -0
  109. package/dist/{host → server}/view.d.ts +7 -7
  110. package/dist/{host → server}/view.js +19 -15
  111. package/dist/server/view.js.map +1 -0
  112. package/dist/server/workflow-runner-content.d.ts +6 -0
  113. package/dist/server/workflow-runner-content.js +96 -0
  114. package/dist/server/workflow-runner-content.js.map +1 -0
  115. package/dist/server/workflow-runner-entry.d.ts +13 -0
  116. package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +89 -57
  117. package/dist/server/workflow-runner-entry.js.map +1 -0
  118. package/dist/server/workflow-runner-protocol.d.ts +69 -0
  119. package/dist/server/workflow-runner-protocol.js +176 -0
  120. package/dist/server/workflow-runner-protocol.js.map +1 -0
  121. package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
  122. package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
  123. package/dist/server/workflow-runner-store.js.map +1 -0
  124. package/dist/server/workflow-runner-supervisor.d.ts +22 -0
  125. package/dist/server/workflow-runner-supervisor.js +56 -0
  126. package/dist/server/workflow-runner-supervisor.js.map +1 -0
  127. package/dist/state/prune.d.ts +1 -1
  128. package/dist/state/prune.js +17 -10
  129. package/dist/state/prune.js.map +1 -1
  130. package/dist/state/schema.js +2 -2
  131. package/dist/state/workflow-messages.d.ts +3 -1
  132. package/dist/state/workflow-messages.js +32 -1
  133. package/dist/state/workflow-messages.js.map +1 -1
  134. package/dist/viewer/backup.js +1 -1
  135. package/dist/viewer/backup.js.map +1 -1
  136. package/dist/viewer/cli.d.ts +2 -2
  137. package/dist/viewer/cli.js +46 -32
  138. package/dist/viewer/cli.js.map +1 -1
  139. package/dist/viewer/tui.d.ts +1 -1
  140. package/dist/viewer/tui.js +1 -1
  141. package/dist/viewer/tui.js.map +1 -1
  142. package/dist/workflows/command-batch.js +2 -2
  143. package/dist/workflows/composition.js +0 -4
  144. package/dist/workflows/composition.js.map +1 -1
  145. package/dist/workflows/definition.js +0 -8
  146. package/dist/workflows/definition.js.map +1 -1
  147. package/dist/workflows/engine.js +18 -20
  148. package/dist/workflows/engine.js.map +1 -1
  149. package/dist/workflows/schema.js +0 -4
  150. package/dist/workflows/schema.js.map +1 -1
  151. package/dist/workflows/store.d.ts +27 -11
  152. package/dist/workflows/store.js +66 -18
  153. package/dist/workflows/store.js.map +1 -1
  154. package/dist/workflows/types.d.ts +3 -5
  155. package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
  156. package/docs/2026-08-25-workflow-follow-ups.md +7 -7
  157. package/docs/2026-08-25-workflow-settings.md +3 -3
  158. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  159. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +5 -5
  160. package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
  161. package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
  162. package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
  163. package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
  164. package/docs/DEFERRED_TURNS.md +7 -7
  165. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  166. package/docs/HUMAN_DECISIONS.md +11 -11
  167. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
  168. package/docs/MONITOR.md +1 -1
  169. package/docs/RESOURCE_MANAGERS.md +221 -0
  170. package/docs/SQLITE_STATE.md +40 -34
  171. package/docs/WORKFLOW_COMPOSITION.md +1 -1
  172. package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +131 -116
  173. package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
  174. package/docs/WORKFLOW_UPDATES.md +16 -16
  175. package/docs/development.md +15 -14
  176. package/docs/live-replay-protocol.md +20 -20
  177. package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
  178. package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
  179. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
  180. package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
  181. package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
  182. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  183. package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
  184. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
  185. package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
  186. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
  187. package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
  188. package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
  189. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
  190. package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
  191. package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
  192. package/docs/tui-viewer.md +9 -9
  193. package/docs/workflows.md +78 -63
  194. package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
  195. package/herdr-plugin.toml +1 -1
  196. package/package.json +5 -5
  197. package/protocol/client.v1.schema.json +7 -7
  198. package/protocol/fixtures/client-v1.json +1 -1
  199. package/src/builtins/monitor.workflow.ts +9 -9
  200. package/src/builtins/pi-agent-group.ts +3 -3
  201. package/src/channels/adapter-entry.ts +14 -14
  202. package/src/client/client.ts +33 -32
  203. package/src/client/protocol.ts +16 -11
  204. package/src/client/resolver.ts +4 -4
  205. package/src/client/view.ts +9 -1
  206. package/src/extension/index.ts +81 -53
  207. package/src/extension/recorder.ts +1 -1
  208. package/src/extension/remote-recorder-store.ts +2 -2
  209. package/src/extension/resource-manager-command.ts +45 -0
  210. package/src/extension/session-delivery.ts +2 -2
  211. package/src/extension/session-view.ts +1 -1
  212. package/src/extension/workflow-message-coordinator.ts +94 -30
  213. package/src/{controllers → resource-managers}/conditions.ts +23 -23
  214. package/src/resource-managers/definition.ts +71 -0
  215. package/src/{controllers → resource-managers}/effects.ts +9 -9
  216. package/src/resource-managers/errors.ts +27 -0
  217. package/src/resource-managers/index.ts +88 -0
  218. package/src/resource-managers/loader.ts +111 -0
  219. package/src/{controllers → resource-managers}/results.ts +4 -4
  220. package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
  221. package/src/{controllers → resource-managers}/sqlite.ts +275 -181
  222. package/src/{controllers → resource-managers}/store.ts +46 -38
  223. package/src/{controllers → resource-managers}/types.ts +50 -41
  224. package/src/{controllers → resource-managers}/workflows.ts +43 -41
  225. package/src/{host → server}/channel-supervisor.ts +8 -8
  226. package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
  227. package/src/{host → server}/processes.ts +3 -3
  228. package/src/{host → server}/resolver-entry.ts +28 -25
  229. package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
  230. package/src/server/resource-runner-protocol.ts +103 -0
  231. package/src/server/resource-runner-supervisor.ts +76 -0
  232. package/src/{host → server}/rpc-bridge.ts +3 -3
  233. package/src/{host → server}/rpc-executor.ts +4 -4
  234. package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
  235. package/src/{host/runner.ts → server/server.ts} +786 -672
  236. package/src/{host → server}/state.ts +78 -67
  237. package/src/{host → server}/view.ts +21 -17
  238. package/src/server/workflow-runner-content.ts +119 -0
  239. package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +131 -82
  240. package/src/server/workflow-runner-protocol.ts +241 -0
  241. package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
  242. package/src/server/workflow-runner-supervisor.ts +79 -0
  243. package/src/state/prune.ts +27 -9
  244. package/src/state/schema.ts +2 -2
  245. package/src/state/workflow-messages.ts +52 -1
  246. package/src/viewer/backup.ts +1 -1
  247. package/src/viewer/cli.ts +50 -36
  248. package/src/viewer/tui.ts +1 -1
  249. package/src/workflows/command-batch.ts +2 -2
  250. package/src/workflows/composition.ts +0 -5
  251. package/src/workflows/definition.ts +0 -8
  252. package/src/workflows/engine.ts +18 -21
  253. package/src/workflows/schema.ts +0 -6
  254. package/src/workflows/store.ts +95 -28
  255. package/src/workflows/types.ts +3 -5
  256. package/dist/controllers/conditions.d.ts +0 -6
  257. package/dist/controllers/conditions.js.map +0 -1
  258. package/dist/controllers/definition.d.ts +0 -6
  259. package/dist/controllers/definition.js +0 -45
  260. package/dist/controllers/definition.js.map +0 -1
  261. package/dist/controllers/effects.d.ts +0 -15
  262. package/dist/controllers/effects.js.map +0 -1
  263. package/dist/controllers/errors.d.ts +0 -12
  264. package/dist/controllers/errors.js +0 -25
  265. package/dist/controllers/errors.js.map +0 -1
  266. package/dist/controllers/index.d.ts +0 -12
  267. package/dist/controllers/index.js +0 -12
  268. package/dist/controllers/index.js.map +0 -1
  269. package/dist/controllers/json.js.map +0 -1
  270. package/dist/controllers/loader.d.ts +0 -23
  271. package/dist/controllers/loader.js +0 -74
  272. package/dist/controllers/loader.js.map +0 -1
  273. package/dist/controllers/manager.d.ts +0 -59
  274. package/dist/controllers/manager.js.map +0 -1
  275. package/dist/controllers/results.d.ts +0 -5
  276. package/dist/controllers/results.js.map +0 -1
  277. package/dist/controllers/sqlite.js.map +0 -1
  278. package/dist/controllers/workflows.d.ts +0 -34
  279. package/dist/controllers/workflows.js.map +0 -1
  280. package/dist/extension/controller-command.js.map +0 -1
  281. package/dist/host/channel-effects.js.map +0 -1
  282. package/dist/host/channel-supervisor.js.map +0 -1
  283. package/dist/host/child-worker-supervisor.js.map +0 -1
  284. package/dist/host/controller-worker-entry.d.ts +0 -2
  285. package/dist/host/controller-worker-entry.js.map +0 -1
  286. package/dist/host/controller-worker-protocol.d.ts +0 -36
  287. package/dist/host/controller-worker-protocol.js +0 -49
  288. package/dist/host/controller-worker-protocol.js.map +0 -1
  289. package/dist/host/controller-worker-supervisor.d.ts +0 -21
  290. package/dist/host/controller-worker-supervisor.js +0 -54
  291. package/dist/host/controller-worker-supervisor.js.map +0 -1
  292. package/dist/host/host-entry.js.map +0 -1
  293. package/dist/host/processes.js.map +0 -1
  294. package/dist/host/resolver-entry.js.map +0 -1
  295. package/dist/host/rpc-bridge.js.map +0 -1
  296. package/dist/host/rpc-executor.js.map +0 -1
  297. package/dist/host/runner.js.map +0 -1
  298. package/dist/host/state.js.map +0 -1
  299. package/dist/host/view.js.map +0 -1
  300. package/dist/host/worker-entry.d.ts +0 -10
  301. package/dist/host/worker-entry.js.map +0 -1
  302. package/dist/host/worker-protocol.d.ts +0 -31
  303. package/dist/host/worker-protocol.js +0 -122
  304. package/dist/host/worker-protocol.js.map +0 -1
  305. package/dist/host/worker-store.js.map +0 -1
  306. package/dist/host/worker-supervisor.d.ts +0 -22
  307. package/dist/host/worker-supervisor.js.map +0 -1
  308. package/docs/CONTROLLERS.md +0 -217
  309. package/src/controllers/definition.ts +0 -65
  310. package/src/controllers/errors.ts +0 -27
  311. package/src/controllers/index.ts +0 -88
  312. package/src/controllers/loader.ts +0 -104
  313. package/src/extension/controller-command.ts +0 -45
  314. package/src/host/controller-worker-protocol.ts +0 -104
  315. package/src/host/controller-worker-supervisor.ts +0 -79
  316. package/src/host/worker-protocol.ts +0 -165
  317. package/src/host/worker-supervisor.ts +0 -74
  318. /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
  319. /package/dist/{controllers → resource-managers}/json.js +0 -0
  320. /package/dist/{controllers → resource-managers}/results.js +0 -0
  321. /package/dist/{controllers → resource-managers}/store.js +0 -0
  322. /package/dist/{controllers → resource-managers}/types.js +0 -0
  323. /package/dist/{host → server}/channel-effects.d.ts +0 -0
  324. /package/dist/{host → server}/channel-effects.js +0 -0
  325. /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
  326. /package/src/{controllers → resource-managers}/json.ts +0 -0
  327. /package/src/{host → server}/channel-effects.ts +0 -0
@@ -5,10 +5,12 @@ import { StateDatabase, workflowStatePath } from "../state/database.js";
5
5
  import { canonicalJson } from "../state/json.js";
6
6
  import { resourceIdFor, tokenHash } from "../state/mutation.js";
7
7
  import { initializeViewerRun, recordViewerDeltas } from "../state/viewer.js";
8
- import { EffectRequestConflictError, ResourceConflictError, ResourceNotFoundError, WorkflowRequestConflictError, } from "./errors.js";
9
- export class SqliteControllerStore {
8
+ import { WorkflowMessageStore } from "../state/workflow-messages.js";
9
+ import { EffectRequestConflictError, ManagedResourceConflictError, ManagedResourceNotFoundError, WorkflowRequestConflictError, } from "./errors.js";
10
+ export class SqliteResourceManagerStore {
10
11
  filePath;
11
12
  state;
13
+ workflowMessages;
12
14
  ownsState;
13
15
  projectId;
14
16
  closed = false;
@@ -22,6 +24,7 @@ export class SqliteControllerStore {
22
24
  checkLegacyState: filePath === workflowStatePath(),
23
25
  });
24
26
  this.filePath = this.state.filePath;
27
+ this.workflowMessages = new WorkflowMessageStore(this.state);
25
28
  if (options.global === true) {
26
29
  this.projectId = null;
27
30
  }
@@ -40,27 +43,30 @@ export class SqliteControllerStore {
40
43
  }
41
44
  putResource(options) {
42
45
  this.assertWritable();
43
- validateName(options.controller, "controller");
46
+ validateName(options.resourceManager, "resource manager");
44
47
  validateKey(options.key, "resource key");
45
48
  const now = epoch(validTimestamp(options.now));
46
49
  const specHash = this.state.putJson(options.spec, now);
47
50
  const initialStatus = {
48
51
  observedGeneration: 0,
49
52
  conditions: [],
50
- controllerStatus: options.initialStatus,
53
+ resourceManagerStatus: options.initialStatus,
51
54
  };
52
55
  const statusHash = this.state.putJson(initialStatus, now);
53
56
  return this.state.transaction(() => {
54
- const existing = this.controllerRow({ controller: options.controller, key: options.key });
57
+ const existing = this.resourceManagerRow({
58
+ resourceManager: options.resourceManager,
59
+ key: options.key,
60
+ });
55
61
  if (existing === undefined) {
56
62
  const uid = randomUUID();
57
- const controllerResourceId = `controller-resource-${randomUUID()}`;
58
- const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.controller}:${options.key}`);
63
+ const managedResourceId = `controller-resource-${randomUUID()}`;
64
+ const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.resourceManager}:${options.key}`);
59
65
  this.state.connection
60
66
  .prepare(`INSERT INTO resources(
61
67
  resource_id, resource_type, aggregate_key, revision, created_at, updated_at
62
68
  ) VALUES (?, 'controller', ?, 1, ?, ?)`)
63
- .run(resourceId, controllerResourceId, now, now);
69
+ .run(resourceId, managedResourceId, now, now);
64
70
  this.state.connection
65
71
  .prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
66
72
  .run(resourceId);
@@ -69,8 +75,8 @@ export class SqliteControllerStore {
69
75
  controller_resource_id, resource_id, project_id, controller_name,
70
76
  resource_key, uid, generation, spec_hash, status_hash, created_at, updated_at
71
77
  ) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`)
72
- .run(controllerResourceId, resourceId, this.requireProjectId(), options.controller, options.key, uid, specHash, statusHash, now, now);
73
- this.enqueueControllerRow(controllerResourceId, now, now);
78
+ .run(managedResourceId, resourceId, this.requireProjectId(), options.resourceManager, options.key, uid, specHash, statusHash, now, now);
79
+ this.enqueueResourceManagerRow(managedResourceId, now, now);
74
80
  this.insertEvent(resourceId, 1, "resource.created", "control", null, { generation: 1 }, now);
75
81
  }
76
82
  else if (!existing.specHash.equals(specHash)) {
@@ -79,26 +85,26 @@ export class SqliteControllerStore {
79
85
  .prepare(`UPDATE controller_resources
80
86
  SET spec_hash = ?, generation = generation + 1, updated_at = ?
81
87
  WHERE controller_resource_id = ?`)
82
- .run(specHash, now, existing.controllerResourceId);
88
+ .run(specHash, now, existing.managedResourceId);
83
89
  this.bumpResource(existing.resourceId, existing.resourceVersion, now);
84
- this.enqueueControllerRow(existing.controllerResourceId, now, now);
90
+ this.enqueueResourceManagerRow(existing.managedResourceId, now, now);
85
91
  this.insertEvent(existing.resourceId, revision, "resource.spec_updated", "control", null, { generation: existing.generation + 1 }, now);
86
92
  }
87
93
  return this.requireResource({
88
- controller: options.controller,
94
+ resourceManager: options.resourceManager,
89
95
  key: options.key,
90
96
  });
91
97
  });
92
98
  }
93
99
  getResource(ref) {
94
- const row = this.controllerRow(ref);
100
+ const row = this.resourceManagerRow(ref);
95
101
  return row === undefined
96
102
  ? undefined
97
- : this.mapControllerResource(row);
103
+ : this.mapManagedResource(row);
98
104
  }
99
105
  getResourceByUid(uid) {
100
- const row = this.state.connection.prepare(controllerSelect("WHERE c.uid = ?")).get(uid);
101
- return isControllerRow(row) ? this.mapControllerResource(row) : undefined;
106
+ const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
107
+ return isResourceManagerRow(row) ? this.mapManagedResource(row) : undefined;
102
108
  }
103
109
  listResources(options = {}) {
104
110
  const clauses = ["1 = 1"];
@@ -107,31 +113,31 @@ export class SqliteControllerStore {
107
113
  clauses.push("c.project_id = ?");
108
114
  params.push(this.projectId);
109
115
  }
110
- if (options.controller !== undefined) {
116
+ if (options.resourceManager !== undefined) {
111
117
  clauses.push("c.controller_name = ?");
112
- params.push(options.controller);
118
+ params.push(options.resourceManager);
113
119
  }
114
120
  const rows = this.state.connection
115
- .prepare(controllerSelect(`WHERE ${clauses.join(" AND ")} ORDER BY c.controller_name, c.resource_key`))
121
+ .prepare(resourceManagerSelect(`WHERE ${clauses.join(" AND ")} ORDER BY c.controller_name, c.resource_key`))
116
122
  .all(...params);
117
123
  return rows
118
- .filter(isControllerRow)
119
- .map((row) => this.mapControllerResource(row));
124
+ .filter(isResourceManagerRow)
125
+ .map((row) => this.mapManagedResource(row));
120
126
  }
121
127
  updateStatus(options) {
122
128
  const now = epoch(validTimestamp(options.now));
123
129
  return this.state.transaction(() => {
124
- const row = this.requireControllerRow(options.ref);
125
- this.assertControllerClaim(row, options.claim, now);
130
+ const row = this.requireResourceManagerRow(options.ref);
131
+ this.assertResourceManagerClaim(row, options.claim, now);
126
132
  if (row.resourceVersion !== options.expectedResourceVersion) {
127
- throw new ResourceConflictError(options.ref.controller, options.ref.key);
133
+ throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
128
134
  }
129
135
  const statusHash = this.state.putJson(options.status, now);
130
136
  this.state.connection
131
137
  .prepare("UPDATE controller_resources SET status_hash = ?, updated_at = ? WHERE controller_resource_id = ?")
132
- .run(statusHash, now, row.controllerResourceId);
138
+ .run(statusHash, now, row.managedResourceId);
133
139
  if (options.finalizers !== undefined)
134
- this.replaceFinalizers(row.controllerResourceId, options.finalizers);
140
+ this.replaceFinalizers(row.managedResourceId, options.finalizers);
135
141
  this.bumpResource(row.resourceId, row.resourceVersion, now);
136
142
  this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.status_updated", "controller", options.claim.ownerId, {}, now, options.claim.generation);
137
143
  options.claim.resourceVersion = row.resourceVersion + 1;
@@ -141,13 +147,13 @@ export class SqliteControllerStore {
141
147
  requestDeletion(ref, nowValue) {
142
148
  const now = epoch(validTimestamp(nowValue));
143
149
  return this.state.transaction(() => {
144
- const row = this.requireControllerRow(ref);
150
+ const row = this.requireResourceManagerRow(ref);
145
151
  if (row.deletionRequestedAt === null) {
146
152
  this.state.connection
147
153
  .prepare("UPDATE controller_resources SET deletion_requested_at = ?, updated_at = ? WHERE controller_resource_id = ?")
148
- .run(now, now, row.controllerResourceId);
154
+ .run(now, now, row.managedResourceId);
149
155
  this.bumpResource(row.resourceId, row.resourceVersion, now);
150
- this.enqueueControllerRow(row.controllerResourceId, now, now);
156
+ this.enqueueResourceManagerRow(row.managedResourceId, now, now);
151
157
  this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.deletion_requested", "control", null, {}, now);
152
158
  }
153
159
  return this.requireResource(ref);
@@ -156,14 +162,14 @@ export class SqliteControllerStore {
156
162
  updateFinalizers(options) {
157
163
  const now = epoch(validTimestamp(options.now));
158
164
  return this.state.transaction(() => {
159
- const row = this.requireControllerRow(options.ref);
165
+ const row = this.requireResourceManagerRow(options.ref);
160
166
  if (row.resourceVersion !== options.expectedResourceVersion) {
161
- throw new ResourceConflictError(options.ref.controller, options.ref.key);
167
+ throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
162
168
  }
163
- this.replaceFinalizers(row.controllerResourceId, options.finalizers);
169
+ this.replaceFinalizers(row.managedResourceId, options.finalizers);
164
170
  this.state.connection
165
171
  .prepare("UPDATE controller_resources SET updated_at = ? WHERE controller_resource_id = ?")
166
- .run(now, row.controllerResourceId);
172
+ .run(now, row.managedResourceId);
167
173
  this.bumpResource(row.resourceId, row.resourceVersion, now);
168
174
  this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.finalizers_updated", "control", null, {}, now);
169
175
  return this.requireResource(options.ref);
@@ -171,37 +177,37 @@ export class SqliteControllerStore {
171
177
  }
172
178
  deleteResource(ref, expectedResourceVersion, claim) {
173
179
  return this.state.transaction(() => {
174
- const row = this.requireControllerRow(ref);
175
- this.assertControllerClaim(row, claim, Date.now());
180
+ const row = this.requireResourceManagerRow(ref);
181
+ this.assertResourceManagerClaim(row, claim, Date.now());
176
182
  if (row.resourceVersion !== expectedResourceVersion) {
177
- throw new ResourceConflictError(ref.controller, ref.key);
183
+ throw new ManagedResourceConflictError(ref.resourceManager, ref.key);
178
184
  }
179
- if (row.deletionRequestedAt === null || this.finalizers(row.controllerResourceId).length > 0)
185
+ if (row.deletionRequestedAt === null || this.finalizers(row.managedResourceId).length > 0)
180
186
  return false;
181
187
  this.state.connection
182
188
  .prepare("DELETE FROM controller_resources WHERE controller_resource_id = ?")
183
- .run(row.controllerResourceId);
189
+ .run(row.managedResourceId);
184
190
  return true;
185
191
  });
186
192
  }
187
193
  enqueue(ref, availableAt) {
188
- const row = this.requireControllerRow(ref);
194
+ const row = this.requireResourceManagerRow(ref);
189
195
  const now = Date.now();
190
- this.enqueueControllerRow(row.controllerResourceId, epoch(validTimestamp(availableAt)), now);
196
+ this.enqueueResourceManagerRow(row.managedResourceId, epoch(validTimestamp(availableAt)), now);
191
197
  }
192
198
  claimNext(options) {
193
- if (options.controllers.length === 0)
199
+ if (options.resourceManagers.length === 0)
194
200
  return undefined;
195
201
  const now = epoch(validTimestamp(options.now));
196
- const placeholders = options.controllers.map(() => "?").join(", ");
202
+ const placeholders = options.resourceManagers.map(() => "?").join(", ");
197
203
  const exclusions = options.exclude ?? [];
198
204
  const exclusionSql = exclusions
199
205
  .map(() => "AND NOT (c.controller_name = ? AND c.resource_key = ?)")
200
206
  .join("\n ");
201
- const exclusionParams = exclusions.flatMap((ref) => [ref.controller, ref.key]);
207
+ const exclusionParams = exclusions.flatMap((ref) => [ref.resourceManager, ref.key]);
202
208
  return this.state.transaction(() => {
203
209
  const row = this.state.connection
204
- .prepare(`${controllerSelect(`
210
+ .prepare(`${resourceManagerSelect(`
205
211
  JOIN controller_queue q ON q.controller_resource_id = c.controller_resource_id
206
212
  JOIN leases l ON l.resource_id = c.resource_id
207
213
  WHERE c.project_id = ?
@@ -211,8 +217,8 @@ export class SqliteControllerStore {
211
217
  AND (l.owner_id IS NULL OR l.expires_at <= ?)
212
218
  ORDER BY q.available_at, c.controller_name, c.resource_key
213
219
  LIMIT 1`)}`)
214
- .get(this.requireProjectId(), ...options.controllers, ...exclusionParams, now, now);
215
- if (!isControllerRow(row))
220
+ .get(this.requireProjectId(), ...options.resourceManagers, ...exclusionParams, now, now);
221
+ if (!isResourceManagerRow(row))
216
222
  return undefined;
217
223
  const lease = this.requireLease(row.resourceId);
218
224
  const token = randomBytes(32).toString("base64url");
@@ -231,14 +237,14 @@ export class SqliteControllerStore {
231
237
  this.bumpResource(row.resourceId, row.resourceVersion, now);
232
238
  this.insertEvent(row.resourceId, row.resourceVersion + 1, "lease.claimed", "controller", options.ownerId, { expiresAt }, now, generation);
233
239
  return {
234
- controller: row.controllerName,
240
+ resourceManager: row.resourceManagerName,
235
241
  key: row.resourceKey,
236
242
  ownerId: options.ownerId,
237
243
  token,
238
244
  generation,
239
- queueVersion: this.queueVersion(row.controllerResourceId),
245
+ queueVersion: this.queueVersion(row.managedResourceId),
240
246
  resourceVersion: row.resourceVersion + 1,
241
- consecutiveErrors: this.queueErrors(row.controllerResourceId),
247
+ consecutiveErrors: this.queueErrors(row.managedResourceId),
242
248
  expiresAt: new Date(expiresAt).toISOString(),
243
249
  };
244
250
  });
@@ -246,7 +252,7 @@ export class SqliteControllerStore {
246
252
  renewClaim(claim, leaseMs, nowValue) {
247
253
  const now = epoch(validTimestamp(nowValue));
248
254
  const expiresAt = now + leaseMs;
249
- const row = this.controllerRow({ controller: claim.controller, key: claim.key });
255
+ const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
250
256
  if (row === undefined)
251
257
  return false;
252
258
  const result = this.state.connection
@@ -259,14 +265,14 @@ export class SqliteControllerStore {
259
265
  return result.changes === 1;
260
266
  }
261
267
  settleClaim(claim, nowValue) {
262
- return this.settleControllerClaim(claim, undefined, nowValue);
268
+ return this.settleResourceManagerClaim(claim, undefined, nowValue);
263
269
  }
264
270
  requeueClaim(claim, options, nowValue) {
265
- return this.settleControllerClaim(claim, options, nowValue);
271
+ return this.settleResourceManagerClaim(claim, options, nowValue);
266
272
  }
267
273
  listQueue() {
268
274
  const rows = this.state.connection
269
- .prepare(`SELECT c.controller_name AS controller, c.resource_key AS resourceKey,
275
+ .prepare(`SELECT c.controller_name AS resourceManager, c.resource_key AS resourceKey,
270
276
  q.available_at AS availableAt, q.consecutive_errors AS consecutiveErrors,
271
277
  l.expires_at AS claimExpiresAt
272
278
  FROM controller_queue q
@@ -275,7 +281,7 @@ export class SqliteControllerStore {
275
281
  WHERE c.project_id = ? ORDER BY q.available_at, c.controller_name, c.resource_key`)
276
282
  .all(this.requireProjectId());
277
283
  return rows.filter(isQueueListRow).map((row) => ({
278
- controller: row.controller,
284
+ resourceManager: row.resourceManager,
279
285
  key: row.resourceKey,
280
286
  availableAt: new Date(row.availableAt).toISOString(),
281
287
  consecutiveErrors: row.consecutiveErrors,
@@ -287,8 +293,8 @@ export class SqliteControllerStore {
287
293
  reserveEffect(options) {
288
294
  const now = epoch(validTimestamp(options.now));
289
295
  return this.state.transaction(() => {
290
- const source = this.requireControllerRowByUid(options.resourceUid);
291
- this.assertControllerClaim(source, options.claim, now);
296
+ const source = this.requireResourceManagerRowByUid(options.resourceUid);
297
+ this.assertResourceManagerClaim(source, options.claim, now);
292
298
  const effectId = effectIdFor(source.resourceId, options.key);
293
299
  const existing = this.effectRow(options.resourceUid, options.key);
294
300
  if (existing !== undefined) {
@@ -335,8 +341,8 @@ export class SqliteControllerStore {
335
341
  updateEffect(options) {
336
342
  const now = epoch(validTimestamp(options.now));
337
343
  return this.state.transaction(() => {
338
- const source = this.requireControllerRowByUid(options.resourceUid);
339
- this.assertControllerClaim(source, options.claim, now);
344
+ const source = this.requireResourceManagerRowByUid(options.resourceUid);
345
+ this.assertResourceManagerClaim(source, options.claim, now);
340
346
  const row = this.requireEffectRow(options.resourceUid, options.key);
341
347
  const effectResourceId = resourceIdFor("effect", row.effectId);
342
348
  const revision = this.resourceRevision(effectResourceId);
@@ -360,8 +366,8 @@ export class SqliteControllerStore {
360
366
  reserveWorkflow(options) {
361
367
  const now = Date.now();
362
368
  return this.state.transaction(() => {
363
- const source = this.requireControllerRowByUid(options.resourceUid);
364
- this.assertControllerClaim(source, options.claim, now);
369
+ const source = this.requireResourceManagerRowByUid(options.resourceUid);
370
+ this.assertResourceManagerClaim(source, options.claim, now);
365
371
  const existing = this.workflowRow(options.resourceUid, options.requestKey);
366
372
  if (existing !== undefined) {
367
373
  if (existing.workflowName !== options.workflow ||
@@ -376,7 +382,7 @@ export class SqliteControllerStore {
376
382
  request_id, controller_resource_id, request_key, workflow_name,
377
383
  input_fingerprint, status, attempt_count, created_at, updated_at
378
384
  ) VALUES (?, ?, ?, ?, ?, 'pending', 0, ?, ?)`)
379
- .run(requestId, source.controllerResourceId, options.requestKey, options.workflow, Buffer.from(options.inputFingerprint, "hex"), now, now);
385
+ .run(requestId, source.managedResourceId, options.requestKey, options.workflow, Buffer.from(options.inputFingerprint, "hex"), now, now);
380
386
  return { record: this.mapWorkflow(this.requireWorkflowRow(requestId)), created: true };
381
387
  });
382
388
  }
@@ -392,8 +398,8 @@ export class SqliteControllerStore {
392
398
  const now = Date.now();
393
399
  return this.state.transaction(() => {
394
400
  const row = this.requireWorkflowRow(requestId);
395
- const source = this.requireControllerRowByUid(row.resourceUid);
396
- this.assertControllerClaim(source, claim, now);
401
+ const source = this.requireResourceManagerRowByUid(row.resourceUid);
402
+ this.assertResourceManagerClaim(source, claim, now);
397
403
  const errorHash = update.error === undefined || update.error === null
398
404
  ? null
399
405
  : this.state.putText(update.error, now);
@@ -419,7 +425,7 @@ export class SqliteControllerStore {
419
425
  if (update.runId !== undefined && row.runId !== null && update.runId !== row.runId) {
420
426
  throw new Error("Scheduler completion run ID does not match the reserved child run");
421
427
  }
422
- const source = this.requireControllerRowByUid(row.resourceUid);
428
+ const source = this.requireResourceManagerRowByUid(row.resourceUid);
423
429
  const now = Date.now();
424
430
  return this.state.transaction(() => {
425
431
  const errorHash = update.error === undefined || update.error === null
@@ -450,9 +456,12 @@ export class SqliteControllerStore {
450
456
  recordEvent(options) {
451
457
  const now = epoch(validTimestamp(options.now));
452
458
  return this.state.transaction(() => {
453
- const row = this.requireControllerRow({ controller: options.controller, key: options.key });
459
+ const row = this.requireResourceManagerRow({
460
+ resourceManager: options.resourceManager,
461
+ key: options.key,
462
+ });
454
463
  if (options.claim !== undefined)
455
- this.assertControllerClaim(row, options.claim, now);
464
+ this.assertResourceManagerClaim(row, options.claim, now);
456
465
  const revision = this.resourceRevision(row.resourceId) + 1;
457
466
  this.bumpResource(row.resourceId, revision - 1, now);
458
467
  const eventId = this.insertEvent(row.resourceId, revision, options.type, options.claim === undefined ? "control" : "controller", options.claim?.ownerId ?? null, options.payload ?? {}, now, options.claim?.generation);
@@ -463,11 +472,11 @@ export class SqliteControllerStore {
463
472
  .get(eventId);
464
473
  /* istanbul ignore if -- exact schema and internal query shape */
465
474
  if (!isSequenceRow(event))
466
- throw new Error("Controller event was not recorded");
475
+ throw new Error("ResourceManager event was not recorded");
467
476
  return {
468
477
  seq: event.seq,
469
478
  recordedAt: new Date(now).toISOString(),
470
- controller: options.controller,
479
+ resourceManager: options.resourceManager,
471
480
  key: options.key,
472
481
  type: options.type,
473
482
  payload: options.payload ?? {},
@@ -481,9 +490,9 @@ export class SqliteControllerStore {
481
490
  clauses.push("c.project_id = ?");
482
491
  params.push(this.projectId);
483
492
  }
484
- if (options.controller !== undefined) {
493
+ if (options.resourceManager !== undefined) {
485
494
  clauses.push("c.controller_name = ?");
486
- params.push(options.controller);
495
+ params.push(options.resourceManager);
487
496
  }
488
497
  if (options.key !== undefined) {
489
498
  clauses.push("c.resource_key = ?");
@@ -492,7 +501,7 @@ export class SqliteControllerStore {
492
501
  params.push(options.limit ?? 100);
493
502
  const rows = this.state.connection
494
503
  .prepare(`SELECT e.event_seq AS seq, e.recorded_at AS recordedAt,
495
- c.controller_name AS controller, c.resource_key AS resourceKey,
504
+ c.controller_name AS resourceManager, c.resource_key AS resourceKey,
496
505
  e.event_type AS eventType, e.payload_hash AS payloadHash
497
506
  FROM events e
498
507
  JOIN resources r ON r.resource_id = e.resource_id
@@ -500,10 +509,10 @@ export class SqliteControllerStore {
500
509
  WHERE ${clauses.join(" AND ")}
501
510
  ORDER BY e.event_seq DESC LIMIT ?`)
502
511
  .all(...params);
503
- return rows.filter(isControllerEventRow).map((row) => ({
512
+ return rows.filter(isResourceManagerEventRow).map((row) => ({
504
513
  seq: row.seq,
505
514
  recordedAt: new Date(row.recordedAt).toISOString(),
506
- controller: row.controller,
515
+ resourceManager: row.resourceManager,
507
516
  key: row.resourceKey,
508
517
  type: row.eventType,
509
518
  payload: row.payloadHash === null ? {} : this.state.readJson(row.payloadHash),
@@ -747,14 +756,14 @@ export class SqliteControllerStore {
747
756
  const now = epoch(validTimestamp(options.now));
748
757
  return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now);
749
758
  }
750
- /** Schedule a worker that validates one pending interactive submission. */
759
+ /** Schedule a runner that validates one pending interactive submission. */
751
760
  claimWorkflowRunForInteractionValidation(options) {
752
761
  const now = epoch(validTimestamp(options.now));
753
762
  return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, {
754
763
  allowPendingInteraction: true,
755
764
  });
756
765
  }
757
- /** Take a short host claim without scheduling a parked interactive run. */
766
+ /** Take a short server claim without scheduling a parked interactive run. */
758
767
  claimWorkflowRunForControl(options) {
759
768
  const now = epoch(validTimestamp(options.now));
760
769
  return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, { allowPendingInteraction: true, preserveQueueStatus: true });
@@ -833,7 +842,7 @@ export class SqliteControllerStore {
833
842
  "(q.affinity_runner_id IS NULL OR q.affinity_runner_id = ? OR q.status IN ('parked', 'starting', 'running'))",
834
843
  "NOT (q.status = 'parked' AND (r.status = 'waiting' OR r.paused = 1))",
835
844
  "NOT EXISTS (SELECT 1 FROM interactive_requests i WHERE i.run_id = r.run_id AND i.status = 'pending')",
836
- "(q.error_code IS NULL OR q.error_code <> 'workflowSourceChanged')",
845
+ "(q.error_code IS NULL OR q.error_code NOT IN ('workflowSourceChanged', 'runnerNoProgress'))",
837
846
  ];
838
847
  const params = [now, now, options.runnerId, options.runnerId];
839
848
  if (this.projectId !== null) {
@@ -907,6 +916,50 @@ export class SqliteControllerStore {
907
916
  parkWorkflowRun(options) {
908
917
  return this.releaseRunClaim(options.runId, options.claimToken, "parked", options.now);
909
918
  }
919
+ parkWorkflowRunForRunnerNoProgress(options) {
920
+ const now = epoch(validTimestamp(options.now));
921
+ return this.state.transaction(() => {
922
+ if (!this.verifyWorkflowRunClaim({
923
+ runId: options.runId,
924
+ claimToken: options.claimToken,
925
+ now: new Date(now).toISOString(),
926
+ })) {
927
+ return false;
928
+ }
929
+ const row = this.requireWorkflowRunRow(options.runId);
930
+ const lease = this.requireLease(row.resourceId);
931
+ const detail = options.detail.slice(0, 8_192);
932
+ const errorHash = this.state.putText(detail, now);
933
+ const run = this.state.connection
934
+ .prepare(`UPDATE runs SET status_detail = ?, updated_at = ?, finished_at = NULL
935
+ WHERE run_id = ? AND status NOT IN ('completed', 'failed', 'timed_out', 'cancelled')`)
936
+ .run(detail, now, options.runId);
937
+ const queue = this.state.connection
938
+ .prepare(`UPDATE run_queue
939
+ SET status = 'parked', error_code = 'runnerNoProgress', error_hash = ?,
940
+ available_at = ?, updated_at = ?, finished_at = NULL
941
+ WHERE run_id = ? AND status IN ('queued', 'starting', 'running', 'parked')`)
942
+ .run(errorHash, now, now, options.runId);
943
+ if (run.changes !== 1 || queue.changes !== 1) {
944
+ throw new Error(`Workflow run ${options.runId} changed during runner recovery`);
945
+ }
946
+ const released = this.state.connection
947
+ .prepare(`UPDATE leases
948
+ SET owner_type = NULL, owner_id = NULL, token_hash = NULL,
949
+ acquired_at = NULL, heartbeat_at = NULL, expires_at = NULL
950
+ WHERE resource_id = ? AND token_hash = ? AND generation = ? AND expires_at > ?`)
951
+ .run(row.resourceId, tokenHash(options.claimToken), lease.generation, now);
952
+ /* istanbul ignore if -- the exact live claim is stable in this transaction */
953
+ if (released.changes !== 1) {
954
+ throw new Error(`Workflow run ${options.runId} claim changed during runner recovery`);
955
+ }
956
+ const revision = this.resourceRevision(row.resourceId);
957
+ this.bumpResource(row.resourceId, revision, now);
958
+ this.insertEvent(row.resourceId, revision + 1, "run.worker_no_progress", lease.ownerType ?? "system", lease.ownerId, { status: "parked", code: "runnerNoProgress" }, now, lease.generation || undefined);
959
+ recordViewerDeltas(this.state, options.runId, [{ targetType: "summary" }, { targetType: "replay" }], now);
960
+ return true;
961
+ });
962
+ }
910
963
  pauseParkedWorkflowRun(options) {
911
964
  const now = epoch(validTimestamp(options.now));
912
965
  return this.state.transaction(() => {
@@ -1125,6 +1178,7 @@ export class SqliteControllerStore {
1125
1178
  if (run.changes !== 1) {
1126
1179
  throw new Error(`Workflow run ${options.runId} has inconsistent durable state`);
1127
1180
  }
1181
+ this.settleWorkflowRunMessages(options.runId, now);
1128
1182
  this.cancelWorkflowRunDependents(options.runId, controlId, errorHash, now);
1129
1183
  const released = this.state.connection
1130
1184
  .prepare(`UPDATE leases
@@ -1261,73 +1315,73 @@ export class SqliteControllerStore {
1261
1315
  }
1262
1316
  requireProjectId() {
1263
1317
  if (this.projectId === null)
1264
- throw new Error("Controller project is not registered in the state database");
1318
+ throw new Error("ResourceManager project is not registered in the state database");
1265
1319
  return this.projectId;
1266
1320
  }
1267
- controllerRow(ref) {
1321
+ resourceManagerRow(ref) {
1268
1322
  if (this.projectId === null)
1269
1323
  return undefined;
1270
1324
  const row = this.state.connection
1271
- .prepare(controllerSelect("WHERE c.project_id = ? AND c.controller_name = ? AND c.resource_key = ?"))
1272
- .get(this.projectId, ref.controller, ref.key);
1273
- return isControllerRow(row) ? row : undefined;
1325
+ .prepare(resourceManagerSelect("WHERE c.project_id = ? AND c.controller_name = ? AND c.resource_key = ?"))
1326
+ .get(this.projectId, ref.resourceManager, ref.key);
1327
+ return isResourceManagerRow(row) ? row : undefined;
1274
1328
  }
1275
- requireControllerRow(ref) {
1276
- const row = this.controllerRow(ref);
1329
+ requireResourceManagerRow(ref) {
1330
+ const row = this.resourceManagerRow(ref);
1277
1331
  if (row === undefined)
1278
- throw new ResourceNotFoundError(ref.controller, ref.key);
1332
+ throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
1279
1333
  return row;
1280
1334
  }
1281
- requireControllerRowByUid(uid) {
1282
- const row = this.state.connection.prepare(controllerSelect("WHERE c.uid = ?")).get(uid);
1335
+ requireResourceManagerRowByUid(uid) {
1336
+ const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
1283
1337
  /* istanbul ignore if -- impossible after exact schema and transaction checks */
1284
- if (!isControllerRow(row))
1285
- throw new Error(`Controller resource not found: ${uid}`);
1338
+ if (!isResourceManagerRow(row))
1339
+ throw new Error(`Managed resource not found: ${uid}`);
1286
1340
  return row;
1287
1341
  }
1288
1342
  requireResource(ref) {
1289
1343
  const resource = this.getResource(ref);
1290
1344
  if (resource === undefined)
1291
- throw new ResourceNotFoundError(ref.controller, ref.key);
1345
+ throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
1292
1346
  return resource;
1293
1347
  }
1294
1348
  /* istanbul ignore next -- pure projection covered by integration tests */
1295
- mapControllerResource(row) {
1349
+ mapManagedResource(row) {
1296
1350
  const spec = this.state.readJson(row.specHash);
1297
1351
  const status = this.state.readJson(row.statusHash);
1298
1352
  return {
1299
1353
  metadata: {
1300
1354
  uid: row.uid,
1301
- controller: row.controllerName,
1355
+ resourceManager: row.resourceManagerName,
1302
1356
  key: row.resourceKey,
1303
1357
  resourceVersion: row.resourceVersion,
1304
1358
  generation: row.generation,
1305
1359
  ...(row.deletionRequestedAt === null
1306
1360
  ? {}
1307
1361
  : { deletionTimestamp: new Date(row.deletionRequestedAt).toISOString() }),
1308
- finalizers: this.finalizers(row.controllerResourceId),
1362
+ finalizers: this.finalizers(row.managedResourceId),
1309
1363
  },
1310
1364
  spec,
1311
1365
  status,
1312
1366
  };
1313
1367
  }
1314
- finalizers(controllerResourceId) {
1368
+ finalizers(managedResourceId) {
1315
1369
  const rows = this.state.connection
1316
1370
  .prepare("SELECT finalizer FROM controller_finalizers WHERE controller_resource_id = ? ORDER BY position")
1317
- .all(controllerResourceId);
1371
+ .all(managedResourceId);
1318
1372
  return rows.flatMap((row) => (isFinalizerRow(row) ? [row.finalizer] : []));
1319
1373
  }
1320
- replaceFinalizers(controllerResourceId, finalizers) {
1374
+ replaceFinalizers(managedResourceId, finalizers) {
1321
1375
  const unique = [...new Set(finalizers)];
1322
1376
  if (unique.length !== finalizers.length)
1323
- throw new Error("Controller finalizers must be unique");
1377
+ throw new Error("ResourceManager finalizers must be unique");
1324
1378
  this.state.connection
1325
1379
  .prepare("DELETE FROM controller_finalizers WHERE controller_resource_id = ?")
1326
- .run(controllerResourceId);
1380
+ .run(managedResourceId);
1327
1381
  const insert = this.state.connection.prepare("INSERT INTO controller_finalizers(controller_resource_id, finalizer, position) VALUES (?, ?, ?)");
1328
- unique.forEach((finalizer, index) => insert.run(controllerResourceId, finalizer, index));
1382
+ unique.forEach((finalizer, index) => insert.run(managedResourceId, finalizer, index));
1329
1383
  }
1330
- enqueueControllerRow(controllerResourceId, availableAt, now) {
1384
+ enqueueResourceManagerRow(managedResourceId, availableAt, now) {
1331
1385
  this.state.connection
1332
1386
  .prepare(`INSERT INTO controller_queue(
1333
1387
  controller_resource_id, available_at, queue_version,
@@ -1337,42 +1391,45 @@ export class SqliteControllerStore {
1337
1391
  available_at = MIN(controller_queue.available_at, excluded.available_at),
1338
1392
  queue_version = controller_queue.queue_version + 1,
1339
1393
  updated_at = excluded.updated_at`)
1340
- .run(controllerResourceId, availableAt, now, now);
1394
+ .run(managedResourceId, availableAt, now, now);
1341
1395
  }
1342
- queueErrors(controllerResourceId) {
1396
+ queueErrors(managedResourceId) {
1343
1397
  const row = this.state.connection
1344
1398
  .prepare("SELECT consecutive_errors AS consecutiveErrors FROM controller_queue WHERE controller_resource_id = ?")
1345
- .get(controllerResourceId);
1399
+ .get(managedResourceId);
1346
1400
  return isErrorCountRow(row) ? row.consecutiveErrors : 0;
1347
1401
  }
1348
- queueVersion(controllerResourceId) {
1402
+ queueVersion(managedResourceId) {
1349
1403
  const row = this.state.connection
1350
1404
  .prepare("SELECT queue_version AS queueVersion FROM controller_queue WHERE controller_resource_id = ?")
1351
- .get(controllerResourceId);
1405
+ .get(managedResourceId);
1352
1406
  /* istanbul ignore if -- exact schema and internal query shape */
1353
1407
  /* istanbul ignore if -- impossible after exact schema and transaction checks */
1354
1408
  if (!isQueueVersionRow(row))
1355
- throw new Error("Controller queue item is missing");
1409
+ throw new Error("ResourceManager queue item is missing");
1356
1410
  return row.queueVersion;
1357
1411
  }
1358
- settleControllerClaim(claim, requeue, nowValue) {
1412
+ settleResourceManagerClaim(claim, requeue, nowValue) {
1359
1413
  const now = epoch(validTimestamp(nowValue));
1360
1414
  return this.state.transaction(() => {
1361
- const row = this.controllerRow({ controller: claim.controller, key: claim.key });
1415
+ const row = this.resourceManagerRow({
1416
+ resourceManager: claim.resourceManager,
1417
+ key: claim.key,
1418
+ });
1362
1419
  if (row === undefined)
1363
1420
  return false;
1364
1421
  try {
1365
- this.assertControllerClaim(row, claim, now, false);
1422
+ this.assertResourceManagerClaim(row, claim, now, false);
1366
1423
  }
1367
1424
  catch {
1368
1425
  return false;
1369
1426
  }
1370
- const currentQueueVersion = this.queueVersion(row.controllerResourceId);
1427
+ const currentQueueVersion = this.queueVersion(row.managedResourceId);
1371
1428
  if (requeue === undefined) {
1372
1429
  if (currentQueueVersion === claim.queueVersion) {
1373
1430
  this.state.connection
1374
1431
  .prepare("DELETE FROM controller_queue WHERE controller_resource_id = ?")
1375
- .run(row.controllerResourceId);
1432
+ .run(row.managedResourceId);
1376
1433
  }
1377
1434
  }
1378
1435
  else {
@@ -1382,15 +1439,15 @@ export class SqliteControllerStore {
1382
1439
  SET available_at = ?, queue_version = queue_version + 1,
1383
1440
  consecutive_errors = consecutive_errors + 1,
1384
1441
  last_error_hash = ?, updated_at = ? WHERE controller_resource_id = ?`)
1385
- .run(epoch(requeue.availableAt), errorHash, now, row.controllerResourceId);
1442
+ .run(epoch(requeue.availableAt), errorHash, now, row.managedResourceId);
1386
1443
  }
1387
1444
  this.releaseLease(row.resourceId, claim, now);
1388
1445
  return true;
1389
1446
  });
1390
1447
  }
1391
- assertControllerClaim(row, claim, now, requireUnexpired = true) {
1392
- if (row.controllerName !== claim.controller || row.resourceKey !== claim.key)
1393
- throw new Error("Controller claim targets another resource");
1448
+ assertResourceManagerClaim(row, claim, now, requireUnexpired = true) {
1449
+ if (row.resourceManagerName !== claim.resourceManager || row.resourceKey !== claim.key)
1450
+ throw new Error("ResourceManager claim targets another resource");
1394
1451
  const lease = this.requireLease(row.resourceId);
1395
1452
  if (lease.ownerType !== "controller" ||
1396
1453
  lease.ownerId !== claim.ownerId ||
@@ -1398,7 +1455,7 @@ export class SqliteControllerStore {
1398
1455
  lease.tokenHash === null ||
1399
1456
  !lease.tokenHash.equals(tokenHash(claim.token)) ||
1400
1457
  (requireUnexpired && (lease.expiresAt === null || lease.expiresAt <= now))) {
1401
- throw new Error("Controller claim is stale");
1458
+ throw new Error("ResourceManager claim is stale");
1402
1459
  }
1403
1460
  }
1404
1461
  requireLease(resourceId) {
@@ -1414,7 +1471,7 @@ export class SqliteControllerStore {
1414
1471
  return row;
1415
1472
  }
1416
1473
  releaseLease(resourceId, claim, now) {
1417
- const row = this.controllerRow({ controller: claim.controller, key: claim.key });
1474
+ const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
1418
1475
  if (row === undefined)
1419
1476
  return;
1420
1477
  const revision = this.resourceRevision(resourceId);
@@ -1489,7 +1546,7 @@ export class SqliteControllerStore {
1489
1546
  resourceUid: row.resourceUid,
1490
1547
  generation: row.generation,
1491
1548
  kind: row.kind,
1492
- state: controllerEffectState(row.state),
1549
+ state: resourceManagerEffectState(row.state),
1493
1550
  requestFingerprint: row.requestFingerprint,
1494
1551
  startedAt: new Date(row.startedAt).toISOString(),
1495
1552
  ...(row.completedAt === null ? {} : { completedAt: new Date(row.completedAt).toISOString() }),
@@ -1769,6 +1826,7 @@ export class SqliteControllerStore {
1769
1826
  updated_at = ?, finished_at = ?
1770
1827
  WHERE run_id = ?`)
1771
1828
  .run(status, errorHash, now, now, runId);
1829
+ this.settleWorkflowRunMessages(runId, now);
1772
1830
  if (status === "cancelled") {
1773
1831
  this.cancelWorkflowRunDependents(runId, lease.ownerId ?? "workflow-control", errorHash, now);
1774
1832
  }
@@ -1778,6 +1836,10 @@ export class SqliteControllerStore {
1778
1836
  return true;
1779
1837
  });
1780
1838
  }
1839
+ settleWorkflowRunMessages(runId, now) {
1840
+ this.workflowMessages.settleOpenTurnsForRun(runId, "lost", now);
1841
+ this.workflowMessages.cancelPendingForRun(runId, now);
1842
+ }
1781
1843
  cancelWorkflowRunDependents(runId, actorId, errorHash, now) {
1782
1844
  this.state.connection
1783
1845
  .prepare(`UPDATE node_attempts
@@ -1840,13 +1902,13 @@ export class SqliteControllerStore {
1840
1902
  }
1841
1903
  assertWritable() {
1842
1904
  if (this.state.mode === "read-only")
1843
- throw new Error("Controller store is read-only");
1905
+ throw new Error("ResourceManager store is read-only");
1844
1906
  }
1845
1907
  }
1846
- function controllerSelect(clause) {
1847
- return `SELECT c.controller_resource_id AS controllerResourceId,
1908
+ function resourceManagerSelect(clause) {
1909
+ return `SELECT c.controller_resource_id AS managedResourceId,
1848
1910
  c.resource_id AS resourceId, r.revision AS resourceVersion,
1849
- c.controller_name AS controllerName, c.resource_key AS resourceKey,
1911
+ c.controller_name AS resourceManagerName, c.resource_key AS resourceKey,
1850
1912
  c.uid, c.generation, c.spec_hash AS specHash, c.status_hash AS statusHash,
1851
1913
  c.deletion_requested_at AS deletionRequestedAt
1852
1914
  FROM controller_resources c JOIN resources r ON r.resource_id = c.resource_id ${clause}`;
@@ -1912,7 +1974,7 @@ function workflowRunViewSelect(clause) {
1912
1974
  function isRecord(value) {
1913
1975
  return typeof value === "object" && value !== null && !Array.isArray(value);
1914
1976
  }
1915
- function isControllerRow(value) {
1977
+ function isResourceManagerRow(value) {
1916
1978
  return isRecord(value);
1917
1979
  }
1918
1980
  function isLeaseRow(value) {
@@ -2007,7 +2069,7 @@ function isWorkflowRow(value) {
2007
2069
  function isQueueListRow(value) {
2008
2070
  return isRecord(value);
2009
2071
  }
2010
- function isControllerEventRow(value) {
2072
+ function isResourceManagerEventRow(value) {
2011
2073
  return isRecord(value);
2012
2074
  }
2013
2075
  function isRunEventRow(value) {
@@ -2151,7 +2213,7 @@ function rowToSource(row) {
2151
2213
  function effectStatus(value) {
2152
2214
  return value === "indeterminate" ? "ambiguous" : value;
2153
2215
  }
2154
- function controllerEffectState(value) {
2216
+ function resourceManagerEffectState(value) {
2155
2217
  return value === "ambiguous" ? "indeterminate" : value;
2156
2218
  }
2157
2219
  function validTimestamp(value) {