@inflexa-ai/harness 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (802) hide show
  1. package/README.md +6 -3
  2. package/dist/agents/conversation-agent.d.ts +19 -3
  3. package/dist/agents/conversation-agent.js +49 -30
  4. package/dist/agents/report-session-agent.d.ts +15 -15
  5. package/dist/agents/report-session-agent.js +23 -15
  6. package/dist/agents/sandbox/index.d.ts +1 -1
  7. package/dist/agents/sandbox/index.js +1 -1
  8. package/dist/agents/sandbox/shared.d.ts +35 -16
  9. package/dist/agents/sandbox/shared.js +63 -24
  10. package/dist/agents/sandbox/translational-safety-agent.js +26 -0
  11. package/dist/agents/sandbox/types.d.ts +26 -3
  12. package/dist/agents/sandbox/validate-skills.d.ts +2 -2
  13. package/dist/agents/sandbox/validate-skills.js +3 -1
  14. package/dist/agents/sandbox-catalog.d.ts +16 -2
  15. package/dist/agents/sandbox-catalog.js +22 -3
  16. package/dist/agents/system-prompt.d.ts +2 -2
  17. package/dist/app/approve-plan.d.ts +5 -2
  18. package/dist/app/approve-plan.js +1 -1
  19. package/dist/app/chat-turn-workflow.d.ts +114 -0
  20. package/dist/app/chat-turn-workflow.js +307 -0
  21. package/dist/app/chat-turn.d.ts +85 -57
  22. package/dist/app/chat-turn.js +125 -99
  23. package/dist/app/data-profile-orientation.js +10 -8
  24. package/dist/app/data-profile-view.d.ts +4 -4
  25. package/dist/app/data-profile-view.js +9 -8
  26. package/dist/app/in-process-turn.d.ts +42 -0
  27. package/dist/app/in-process-turn.js +139 -0
  28. package/dist/app/message-assembly.d.ts +10 -4
  29. package/dist/app/message-assembly.js +13 -8
  30. package/dist/app/report-session-runtime.d.ts +4 -4
  31. package/dist/app/report-session-runtime.js +36 -21
  32. package/dist/app/spawn-report-session.d.ts +33 -28
  33. package/dist/app/spawn-report-session.js +25 -40
  34. package/dist/app/synthesize-run.d.ts +22 -16
  35. package/dist/app/synthesize-run.js +41 -35
  36. package/dist/app/work.d.ts +96 -0
  37. package/dist/app/work.js +147 -0
  38. package/dist/auth/local-auth-context.d.ts +1 -3
  39. package/dist/auth/local-auth-context.js +1 -3
  40. package/dist/auth/local-run-authorizer.d.ts +1 -1
  41. package/dist/auth/local-run-authorizer.js +5 -3
  42. package/dist/auth/types.d.ts +26 -26
  43. package/dist/billing/cached-resolver.d.ts +4 -4
  44. package/dist/billing/run-charge.d.ts +5 -8
  45. package/dist/billing/run-charge.js +2 -2
  46. package/dist/billing/usage-recorder.d.ts +9 -9
  47. package/dist/citations/aggregate.js +1 -1
  48. package/dist/citations/clients/arxiv.d.ts +2 -2
  49. package/dist/citations/clients/arxiv.js +12 -4
  50. package/dist/citations/clients/common.js +4 -2
  51. package/dist/citations/clients/crossref.d.ts +2 -2
  52. package/dist/citations/clients/crossref.js +37 -37
  53. package/dist/citations/clients/doi-registry.js +15 -16
  54. package/dist/citations/clients/pubmed.d.ts +2 -2
  55. package/dist/citations/clients/pubmed.js +20 -13
  56. package/dist/citations/clients/semantic-scholar.d.ts +2 -2
  57. package/dist/citations/clients/semantic-scholar.js +9 -9
  58. package/dist/citations/compare.js +2 -2
  59. package/dist/citations/match.d.ts +2 -2
  60. package/dist/citations/match.js +12 -7
  61. package/dist/citations/normalize.js +35 -26
  62. package/dist/citations/plan.js +6 -2
  63. package/dist/citations/resolve.d.ts +6 -6
  64. package/dist/citations/resolve.js +34 -25
  65. package/dist/citations/types.d.ts +77 -77
  66. package/dist/config/environment-stores.d.ts +10 -8
  67. package/dist/config/resource-limits.js +1 -3
  68. package/dist/contracts/analogy-report.d.ts +7 -7
  69. package/dist/contracts/analogy-report.js +2 -2
  70. package/dist/contracts/chat-events.d.ts +46 -22
  71. package/dist/contracts/chat-events.js +1 -1
  72. package/dist/contracts/chat-frame.d.ts +15 -5
  73. package/dist/contracts/chat-frame.js +67 -40
  74. package/dist/contracts/chat-parts.d.ts +130 -67
  75. package/dist/contracts/chat-parts.js +1 -0
  76. package/dist/contracts/content-url.js +16 -2
  77. package/dist/contracts/data-profile.d.ts +38 -38
  78. package/dist/contracts/index.d.ts +3 -3
  79. package/dist/contracts/index.js +1 -1
  80. package/dist/contracts/message.d.ts +6 -6
  81. package/dist/contracts/organ-system.d.ts +11 -11
  82. package/dist/contracts/part-registry.d.ts +5 -4
  83. package/dist/contracts/part-registry.js +5 -0
  84. package/dist/contracts/profile-vocabulary.d.ts +11 -10
  85. package/dist/contracts/profile-vocabulary.js +10 -3
  86. package/dist/contracts/report-blocks.d.ts +329 -329
  87. package/dist/contracts/report-reference.d.ts +105 -81
  88. package/dist/contracts/report-reference.js +30 -13
  89. package/dist/contracts/schemas/chat-events.d.ts +45 -6
  90. package/dist/contracts/schemas/chat-events.js +16 -0
  91. package/dist/contracts/schemas/chat-parts.d.ts +214 -102
  92. package/dist/contracts/schemas/chat-parts.js +50 -7
  93. package/dist/contracts/schemas/index.d.ts +1 -1
  94. package/dist/contracts/schemas/index.js +1 -1
  95. package/dist/contracts/severity.d.ts +1 -1
  96. package/dist/contracts/structure-source.d.ts +3 -3
  97. package/dist/contracts/structure-source.js +6 -2
  98. package/dist/contracts/subagent-activity.d.ts +2 -2
  99. package/dist/contracts/subagent-activity.js +5 -3
  100. package/dist/contracts/usage.d.ts +2 -2
  101. package/dist/data/safety-panel-schema.d.ts +25 -25
  102. package/dist/data/safety-panel-schema.js +13 -4
  103. package/dist/execution/artifact-metadata.d.ts +23 -15
  104. package/dist/execution/artifact-metadata.js +33 -10
  105. package/dist/execution/artifact-record.d.ts +2 -2
  106. package/dist/execution/artifact-registration.d.ts +5 -5
  107. package/dist/execution/artifact-registry.d.ts +8 -8
  108. package/dist/execution/dbos-run-launcher.d.ts +24 -4
  109. package/dist/execution/dbos-run-launcher.js +58 -4
  110. package/dist/execution/dbos-workflow-purger.d.ts +7 -6
  111. package/dist/execution/dbos-workflow-purger.js +29 -21
  112. package/dist/execution/input-signature.d.ts +2 -2
  113. package/dist/execution/post-step-pipeline.d.ts +9 -9
  114. package/dist/execution/post-step-pipeline.js +1 -1
  115. package/dist/execution/post-step.js +2 -2
  116. package/dist/execution/reconcile-manifest.d.ts +4 -4
  117. package/dist/execution/reconcile-manifest.js +3 -2
  118. package/dist/execution/run-authorizer.d.ts +46 -37
  119. package/dist/execution/run-authorizer.js +8 -8
  120. package/dist/execution/run-canceler.d.ts +39 -17
  121. package/dist/execution/run-canceler.js +137 -28
  122. package/dist/execution/run-end-record.d.ts +29 -0
  123. package/dist/execution/run-end-record.js +27 -0
  124. package/dist/execution/run-event-parts.d.ts +0 -8
  125. package/dist/execution/run-event-parts.js +29 -24
  126. package/dist/execution/run-event-stream.d.ts +8 -8
  127. package/dist/execution/run-event-stream.js +3 -4
  128. package/dist/execution/run-launcher.d.ts +4 -4
  129. package/dist/execution/run-synthesis.d.ts +49 -48
  130. package/dist/execution/run-synthesis.js +96 -255
  131. package/dist/execution/staged-input.d.ts +2 -2
  132. package/dist/execution/step-summary.d.ts +6 -6
  133. package/dist/execution/step-summary.js +4 -8
  134. package/dist/execution/topo-sort.js +4 -3
  135. package/dist/execution/workflow-purger.d.ts +2 -2
  136. package/dist/index.d.ts +25 -24
  137. package/dist/index.js +36 -37
  138. package/dist/input-sanitization.js +9 -2
  139. package/dist/input-scan/clustering.d.ts +6 -6
  140. package/dist/input-scan/clustering.js +11 -5
  141. package/dist/input-scan/companions.js +3 -2
  142. package/dist/input-scan/detect-sets.d.ts +2 -2
  143. package/dist/input-scan/detect-sets.js +25 -21
  144. package/dist/input-scan/enrich.d.ts +6 -6
  145. package/dist/input-scan/enrich.js +29 -12
  146. package/dist/input-scan/formats.d.ts +2 -2
  147. package/dist/input-scan/formats.js +8 -2
  148. package/dist/input-scan/markers.d.ts +2 -2
  149. package/dist/input-scan/markers.js +14 -5
  150. package/dist/input-scan/menu.d.ts +10 -10
  151. package/dist/input-scan/menu.js +0 -0
  152. package/dist/input-scan/quarantine.d.ts +2 -2
  153. package/dist/input-scan/quarantine.js +23 -18
  154. package/dist/input-scan/readout-budget.d.ts +2 -2
  155. package/dist/input-scan/readout-budget.js +1 -1
  156. package/dist/input-scan/readout.d.ts +2 -2
  157. package/dist/input-scan/readout.js +47 -34
  158. package/dist/input-scan/scan.d.ts +2 -2
  159. package/dist/input-scan/scan.js +7 -5
  160. package/dist/input-scan/set-types.d.ts +28 -28
  161. package/dist/input-scan/shapes.d.ts +4 -4
  162. package/dist/input-scan/shapes.js +0 -0
  163. package/dist/input-scan/templates.d.ts +10 -10
  164. package/dist/input-scan/templates.js +30 -22
  165. package/dist/input-scan/tokens.d.ts +6 -4
  166. package/dist/input-scan/tokens.js +100 -38
  167. package/dist/input-scan/types.d.ts +22 -22
  168. package/dist/lib/async-utils.d.ts +3 -7
  169. package/dist/lib/async-utils.js +5 -9
  170. package/dist/lib/cache.js +1 -1
  171. package/dist/lib/chrome.d.ts +46 -6
  172. package/dist/lib/console-logger.js +2 -2
  173. package/dist/lib/db-result.d.ts +22 -6
  174. package/dist/lib/db-result.js +9 -13
  175. package/dist/lib/eyes.d.ts +4 -4
  176. package/dist/lib/eyes.js +1 -1
  177. package/dist/lib/fs-helpers.js +18 -6
  178. package/dist/lib/fs-result.d.ts +1 -0
  179. package/dist/lib/fs-result.js +5 -6
  180. package/dist/lib/hooks.d.ts +23 -7
  181. package/dist/lib/hooks.js +17 -3
  182. package/dist/lib/host-concurrency.js +3 -2
  183. package/dist/lib/logger.d.ts +2 -2
  184. package/dist/lib/logger.js +2 -1
  185. package/dist/lib/metrics.d.ts +1 -1
  186. package/dist/lib/null-proto.d.ts +6 -0
  187. package/dist/lib/null-proto.js +17 -0
  188. package/dist/lib/otel-spans.d.ts +1 -1
  189. package/dist/lib/otel-spans.js +9 -5
  190. package/dist/lib/otel.d.ts +4 -2
  191. package/dist/lib/otel.js +10 -2
  192. package/dist/lib/page-capture.d.ts +8 -8
  193. package/dist/lib/page-capture.js +9 -4
  194. package/dist/lib/parse-json.d.ts +5 -0
  195. package/dist/lib/parse-json.js +12 -0
  196. package/dist/lib/present.d.ts +12 -0
  197. package/dist/lib/present.js +39 -0
  198. package/dist/lib/rate-limit.d.ts +4 -4
  199. package/dist/lib/rate-limit.js +12 -13
  200. package/dist/lib/regulatory-corpus/fetch-fda-guidance.d.ts +4 -4
  201. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +4 -10
  202. package/dist/lib/regulatory-corpus/fetch-ich.d.ts +4 -4
  203. package/dist/lib/regulatory-corpus/fetch-ich.js +18 -14
  204. package/dist/lib/regulatory-corpus/normalize-whitespace.d.ts +6 -0
  205. package/dist/lib/regulatory-corpus/normalize-whitespace.js +41 -0
  206. package/dist/lib/regulatory-corpus.d.ts +10 -10
  207. package/dist/lib/regulatory-corpus.js +3 -1
  208. package/dist/lib/result.d.ts +10 -11
  209. package/dist/lib/result.js +15 -13
  210. package/dist/lib/storage.d.ts +2 -2
  211. package/dist/lib/test/async-iterable.d.ts +9 -0
  212. package/dist/lib/test/async-iterable.js +28 -0
  213. package/dist/lib/test/request-url.d.ts +5 -0
  214. package/dist/lib/test/request-url.js +7 -0
  215. package/dist/lib/test/result-assert.d.ts +12 -0
  216. package/dist/lib/test/result-assert.js +17 -0
  217. package/dist/lib/test/text-shapes.d.ts +24 -0
  218. package/dist/lib/test/text-shapes.js +63 -0
  219. package/dist/lib/text.d.ts +22 -0
  220. package/dist/lib/text.js +68 -0
  221. package/dist/lib/thrown.d.ts +18 -0
  222. package/dist/lib/thrown.js +30 -0
  223. package/dist/lib/unknown-array.d.ts +6 -0
  224. package/dist/lib/unknown-array.js +8 -0
  225. package/dist/lib/unknown-containers.d.ts +14 -0
  226. package/dist/lib/unknown-containers.js +17 -0
  227. package/dist/lib/unknown-text.d.ts +8 -0
  228. package/dist/lib/unknown-text.js +28 -0
  229. package/dist/lib/zod-issue-shape.d.ts +1 -1
  230. package/dist/lib/zod-issue-shape.js +31 -31
  231. package/dist/lib/zod-issues.d.ts +3 -3
  232. package/dist/lib/zod-issues.js +30 -17
  233. package/dist/literature/sources/arxiv.d.ts +6 -6
  234. package/dist/literature/sources/arxiv.js +92 -35
  235. package/dist/literature/sources/http.d.ts +2 -2
  236. package/dist/literature/sources/http.js +16 -15
  237. package/dist/literature/sources/pubmed.d.ts +16 -16
  238. package/dist/literature/sources/pubmed.js +96 -61
  239. package/dist/literature/sources/semantic-scholar.d.ts +8 -8
  240. package/dist/literature/sources/semantic-scholar.js +22 -13
  241. package/dist/loop/accounting.d.ts +63 -0
  242. package/dist/loop/accounting.js +141 -0
  243. package/dist/loop/call-policy.d.ts +73 -0
  244. package/dist/loop/call-policy.js +109 -0
  245. package/dist/loop/chat-stream.d.ts +62 -0
  246. package/dist/loop/chat-stream.js +237 -0
  247. package/dist/loop/compaction.d.ts +14 -9
  248. package/dist/loop/compaction.js +17 -0
  249. package/dist/loop/metrics.d.ts +12 -15
  250. package/dist/loop/metrics.js +14 -26
  251. package/dist/loop/run-loop.d.ts +233 -0
  252. package/dist/loop/run-loop.js +770 -0
  253. package/dist/loop/sub-agent.d.ts +61 -0
  254. package/dist/loop/sub-agent.js +161 -0
  255. package/dist/loop/tool-adapter.d.ts +98 -0
  256. package/dist/loop/tool-adapter.js +364 -0
  257. package/dist/loop/tool-detail.d.ts +1 -1
  258. package/dist/loop/tool-detail.js +6 -5
  259. package/dist/loop/tool-outcome.d.ts +1 -1
  260. package/dist/loop/tool-output.d.ts +4 -4
  261. package/dist/loop/tool-output.js +2 -2
  262. package/dist/loop/turn-credentials.d.ts +34 -0
  263. package/dist/loop/turn-credentials.js +73 -0
  264. package/dist/loop/types.d.ts +34 -37
  265. package/dist/loop/types.js +2 -7
  266. package/dist/memory/ai-sdk-message-storage.d.ts +5 -5
  267. package/dist/memory/ai-sdk-message-storage.js +13 -11
  268. package/dist/memory/card-builders.d.ts +14 -14
  269. package/dist/memory/card-builders.js +19 -15
  270. package/dist/memory/conversation-display-recorder.d.ts +22 -6
  271. package/dist/memory/conversation-display-recorder.js +64 -20
  272. package/dist/memory/conversation-display-replay.js +3 -2
  273. package/dist/memory/conversation-display-storage.d.ts +15 -7
  274. package/dist/memory/conversation-display-storage.js +141 -36
  275. package/dist/memory/conversation-view.d.ts +4 -4
  276. package/dist/memory/conversation-view.js +5 -4
  277. package/dist/memory/count-tokens.js +10 -8
  278. package/dist/memory/derive-thread-title.d.ts +0 -13
  279. package/dist/memory/derive-thread-title.js +2 -1
  280. package/dist/memory/message-backfill.d.ts +12 -5
  281. package/dist/memory/message-backfill.js +5 -3
  282. package/dist/memory/thread-history.d.ts +66 -24
  283. package/dist/memory/thread-history.js +212 -79
  284. package/dist/memory/thread-store.d.ts +26 -17
  285. package/dist/memory/thread-store.js +15 -9
  286. package/dist/memory/tool-call-integrity.d.ts +2 -2
  287. package/dist/memory/tool-call-integrity.js +4 -3
  288. package/dist/memory/working-memory.d.ts +16 -12
  289. package/dist/memory/working-memory.js +72 -61
  290. package/dist/prompts/briefing.d.ts +63 -9
  291. package/dist/prompts/briefing.js +130 -3
  292. package/dist/prompts/planner.js +13 -2
  293. package/dist/prompts/synthesis-agent.d.ts +1 -1
  294. package/dist/prompts/synthesis-agent.js +6 -14
  295. package/dist/provenance/collector.d.ts +6 -6
  296. package/dist/provenance/collector.js +15 -8
  297. package/dist/provenance/exec-frame.d.ts +2 -2
  298. package/dist/provenance/exec-frame.js +6 -1
  299. package/dist/provenance/seam.d.ts +24 -19
  300. package/dist/provenance/seam.js +7 -5
  301. package/dist/provenance/types.d.ts +6 -6
  302. package/dist/providers/ai-sdk.d.ts +113 -62
  303. package/dist/providers/ai-sdk.js +303 -458
  304. package/dist/providers/embedding.d.ts +2 -2
  305. package/dist/providers/embedding.js +1 -1
  306. package/dist/providers/errors.d.ts +22 -2
  307. package/dist/providers/errors.js +98 -50
  308. package/dist/providers/prompt-cache.d.ts +8 -8
  309. package/dist/providers/prompt-cache.js +13 -11
  310. package/dist/providers/reasoning.d.ts +2 -2
  311. package/dist/providers/reasoning.js +2 -2
  312. package/dist/providers/types.d.ts +59 -92
  313. package/dist/reference-data/catalog.d.ts +2 -15
  314. package/dist/reference-data/catalog.js +166 -10
  315. package/dist/reference-data/receipt.d.ts +2 -2
  316. package/dist/report-model/assert-rules.js +1 -1
  317. package/dist/report-model/authoring-grammar.d.ts +209 -231
  318. package/dist/report-model/block-walk.d.ts +10 -10
  319. package/dist/report-model/block-walk.js +2 -0
  320. package/dist/report-model/draft-finish.d.ts +8 -8
  321. package/dist/report-model/draft-finish.js +1 -1
  322. package/dist/report-model/draft-hash.js +8 -7
  323. package/dist/report-model/draft-operations.d.ts +22 -10
  324. package/dist/report-model/draft-operations.js +48 -30
  325. package/dist/report-model/draft-read.d.ts +19 -7
  326. package/dist/report-model/draft-read.js +16 -6
  327. package/dist/report-model/draft.d.ts +844 -2
  328. package/dist/report-model/draft.js +3 -1
  329. package/dist/report-model/fixture-resolver.js +39 -16
  330. package/dist/report-model/pin-snapshot.d.ts +2 -2
  331. package/dist/report-model/pin-snapshot.js +15 -15
  332. package/dist/report-model/production-resolver.d.ts +18 -18
  333. package/dist/report-model/production-resolver.js +50 -42
  334. package/dist/report-model/reference-resolver.d.ts +26 -10
  335. package/dist/report-model/reference-resolver.js +68 -5
  336. package/dist/report-model/reference-stamp.js +30 -38
  337. package/dist/report-model/structural-validation.js +12 -4
  338. package/dist/report-model/validate.d.ts +7 -7
  339. package/dist/report-model/validate.js +6 -6
  340. package/dist/report-render/assets.d.ts +2 -2
  341. package/dist/report-render/assets.js +27 -3
  342. package/dist/report-render/chart-export.d.ts +10 -2
  343. package/dist/report-render/chart-export.js +37 -11
  344. package/dist/report-render/chart-presets.d.ts +8 -8
  345. package/dist/report-render/chart-presets.js +3 -3
  346. package/dist/report-render/chart-renderers.d.ts +23 -16
  347. package/dist/report-render/chart-renderers.js +74 -53
  348. package/dist/report-render/chart-toolbox.d.ts +2 -2
  349. package/dist/report-render/chart-toolbox.js +7 -6
  350. package/dist/report-render/chart.d.ts +15 -16
  351. package/dist/report-render/chart.js +369 -252
  352. package/dist/report-render/design.d.ts +36 -297
  353. package/dist/report-render/design.js +0 -12
  354. package/dist/report-render/figures/common.d.ts +12 -12
  355. package/dist/report-render/figures/common.js +42 -31
  356. package/dist/report-render/figures/dendrogram.d.ts +3 -3
  357. package/dist/report-render/figures/dendrogram.js +10 -10
  358. package/dist/report-render/figures/dense.js +15 -8
  359. package/dist/report-render/figures/dotplot.js +46 -33
  360. package/dist/report-render/figures/embedding.js +52 -43
  361. package/dist/report-render/figures/equal-units.d.ts +10 -10
  362. package/dist/report-render/figures/equal-units.js +27 -27
  363. package/dist/report-render/figures/forest.js +9 -7
  364. package/dist/report-render/figures/gsea.js +21 -11
  365. package/dist/report-render/figures/heatmap.js +13 -9
  366. package/dist/report-render/figures/index.d.ts +10 -10
  367. package/dist/report-render/figures/index.js +4 -2
  368. package/dist/report-render/figures/km.js +10 -9
  369. package/dist/report-render/figures/label-room.d.ts +12 -12
  370. package/dist/report-render/figures/label-room.js +7 -2
  371. package/dist/report-render/figures/locuszoom.d.ts +2 -2
  372. package/dist/report-render/figures/locuszoom.js +59 -31
  373. package/dist/report-render/figures/lollipop.js +3 -2
  374. package/dist/report-render/figures/ma.js +8 -4
  375. package/dist/report-render/figures/manhattan.d.ts +1 -2
  376. package/dist/report-render/figures/manhattan.js +52 -34
  377. package/dist/report-render/figures/oncoprint.js +4 -5
  378. package/dist/report-render/figures/pca.js +25 -11
  379. package/dist/report-render/figures/qq.js +20 -9
  380. package/dist/report-render/figures/roc.js +1 -1
  381. package/dist/report-render/figures/sankey.d.ts +2 -2
  382. package/dist/report-render/figures/sankey.js +11 -10
  383. package/dist/report-render/figures/upset.d.ts +6 -6
  384. package/dist/report-render/figures/upset.js +7 -4
  385. package/dist/report-render/figures/volcano.js +12 -10
  386. package/dist/report-render/hybrid-svg.d.ts +4 -4
  387. package/dist/report-render/hybrid-svg.js +7 -5
  388. package/dist/report-render/number-format.d.ts +4 -4
  389. package/dist/report-render/number-format.js +62 -27
  390. package/dist/report-render/page.js +2 -1
  391. package/dist/report-render/provenance-data.d.ts +2 -2
  392. package/dist/report-render/references.d.ts +4 -4
  393. package/dist/report-render/render.d.ts +2 -2
  394. package/dist/report-render/render.js +10 -11
  395. package/dist/report-render/script-json.js +1 -1
  396. package/dist/report-render/table-data.d.ts +6 -6
  397. package/dist/report-render/test/options.d.ts +49 -0
  398. package/dist/report-render/test/options.js +143 -0
  399. package/dist/report-render/test/page-function.d.ts +19 -0
  400. package/dist/report-render/test/page-function.js +33 -0
  401. package/dist/report-render/types.d.ts +10 -10
  402. package/dist/report-render/value-bridge.d.ts +4 -4
  403. package/dist/report-render/value-bridge.js +27 -14
  404. package/dist/report-render/views/chart-view.d.ts +2 -2
  405. package/dist/report-render/views/chart-view.js +2 -2
  406. package/dist/report-render/views/lineage.d.ts +6 -6
  407. package/dist/report-render/views/lineage.js +2 -0
  408. package/dist/report-render/views/page-view.d.ts +2 -2
  409. package/dist/report-render/views/page-view.js +1 -0
  410. package/dist/report-render/views/prose.js +25 -8
  411. package/dist/report-render/views/references-view.js +12 -1
  412. package/dist/report-render/views/values.js +1 -1
  413. package/dist/runtime/assemble.d.ts +53 -26
  414. package/dist/runtime/assemble.js +103 -56
  415. package/dist/runtime/boot.d.ts +8 -7
  416. package/dist/runtime/boot.js +1 -1
  417. package/dist/runtime/connection-budget.d.ts +6 -6
  418. package/dist/runtime/connection-budget.js +1 -1
  419. package/dist/runtime/dbos.d.ts +29 -19
  420. package/dist/runtime/dbos.js +35 -20
  421. package/dist/runtime/result-serialization.js +18 -5
  422. package/dist/runtime/shutdown.d.ts +2 -2
  423. package/dist/sandbox/client.d.ts +7 -3
  424. package/dist/sandbox/cpu-files.d.ts +2 -2
  425. package/dist/sandbox/cpu-files.js +21 -4
  426. package/dist/sandbox/create-sandbox.d.ts +7 -8
  427. package/dist/sandbox/create-sandbox.js +6 -12
  428. package/dist/sandbox/docker-client.d.ts +48 -3
  429. package/dist/sandbox/docker-client.js +10 -6
  430. package/dist/sandbox/estimate-data-profile-resources.js +2 -7
  431. package/dist/sandbox/exec-outcome.d.ts +2 -2
  432. package/dist/sandbox/exec-outcome.js +1 -1
  433. package/dist/sandbox/exec.d.ts +10 -6
  434. package/dist/sandbox/exec.js +27 -12
  435. package/dist/sandbox/execution-id.js +14 -6
  436. package/dist/sandbox/farm.d.ts +2 -2
  437. package/dist/sandbox/farm.js +22 -27
  438. package/dist/sandbox/identity.d.ts +7 -0
  439. package/dist/sandbox/identity.js +14 -3
  440. package/dist/sandbox/image-packages.d.ts +26 -4
  441. package/dist/sandbox/image-packages.js +29 -27
  442. package/dist/sandbox/k8s-client.d.ts +35 -8
  443. package/dist/sandbox/k8s-client.js +6 -5
  444. package/dist/sandbox/labels.js +1 -0
  445. package/dist/sandbox/liveness.js +3 -1
  446. package/dist/sandbox/mount-plan.d.ts +10 -10
  447. package/dist/sandbox/mount-plan.js +13 -14
  448. package/dist/sandbox/notification-sweep.d.ts +4 -4
  449. package/dist/sandbox/notification-sweep.js +4 -7
  450. package/dist/sandbox/package-identity.d.ts +6 -13
  451. package/dist/sandbox/package-identity.js +46 -28
  452. package/dist/sandbox/reaper.d.ts +10 -10
  453. package/dist/sandbox/reaper.js +4 -6
  454. package/dist/sandbox/sandbox-error.d.ts +2 -9
  455. package/dist/sandbox/sandbox-error.js +21 -8
  456. package/dist/sandbox/sandbox-step-translate.d.ts +8 -5
  457. package/dist/sandbox/sandbox-step-translate.js +22 -8
  458. package/dist/sandbox/step-sandbox.d.ts +47 -0
  459. package/dist/sandbox/step-sandbox.js +53 -0
  460. package/dist/sandbox/types.d.ts +18 -19
  461. package/dist/sandbox/types.js +1 -1
  462. package/dist/schemas/artifact-manifest.d.ts +4 -4
  463. package/dist/schemas/data-profile-schemas.d.ts +351 -306
  464. package/dist/schemas/file-metadata.js +2 -4
  465. package/dist/schemas/plan-schemas.d.ts +79 -25
  466. package/dist/schemas/plan-schemas.js +1 -1
  467. package/dist/schemas/run-synthesis.d.ts +95 -26
  468. package/dist/schemas/run-synthesis.js +3 -1
  469. package/dist/schemas/validate-plan.d.ts +4 -4
  470. package/dist/schemas/validate-plan.js +17 -5
  471. package/dist/schemas/workflow-state.d.ts +127 -14
  472. package/dist/schemas/workflow-state.js +56 -2
  473. package/dist/state/active-turns.d.ts +36 -0
  474. package/dist/state/active-turns.js +67 -0
  475. package/dist/state/analyses-metrics.d.ts +8 -8
  476. package/dist/state/analyses-metrics.js +4 -4
  477. package/dist/state/analyses.d.ts +0 -14
  478. package/dist/state/analyses.js +0 -32
  479. package/dist/state/artifacts.d.ts +47 -18
  480. package/dist/state/artifacts.js +48 -7
  481. package/dist/state/data-profile.d.ts +2 -2
  482. package/dist/state/db.d.ts +12 -2
  483. package/dist/state/db.js +5 -0
  484. package/dist/state/edit-intents.d.ts +4 -0
  485. package/dist/state/edit-intents.js +71 -0
  486. package/dist/state/index.d.ts +5 -5
  487. package/dist/state/index.js +3 -3
  488. package/dist/state/init.d.ts +2 -1
  489. package/dist/state/init.js +5 -2
  490. package/dist/state/migrations/20260927120000_baseline.js +3 -1
  491. package/dist/state/migrations/20261026120000_add_the_edit_intents.d.ts +7 -0
  492. package/dist/state/migrations/20261026120000_add_the_edit_intents.js +21 -0
  493. package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.d.ts +7 -0
  494. package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.js +16 -0
  495. package/dist/state/migrations/20261027120100_store_the_authoring_results.d.ts +8 -0
  496. package/dist/state/migrations/20261027120100_store_the_authoring_results.js +20 -0
  497. package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.d.ts +7 -0
  498. package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.js +12 -0
  499. package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.d.ts +7 -0
  500. package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.js +11 -0
  501. package/dist/state/migrations/20261028120000_record_the_paused_work.d.ts +7 -0
  502. package/dist/state/migrations/20261028120000_record_the_paused_work.js +25 -0
  503. package/dist/state/migrations/20261028120100_end_the_suspended_runs.d.ts +8 -0
  504. package/dist/state/migrations/20261028120100_end_the_suspended_runs.js +13 -0
  505. package/dist/state/migrations/20261029120000_reference_the_run_mandate.d.ts +7 -0
  506. package/dist/state/migrations/20261029120000_reference_the_run_mandate.js +23 -0
  507. package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.d.ts +7 -0
  508. package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.js +11 -0
  509. package/dist/state/migrations/index.js +18 -0
  510. package/dist/state/paused-work.d.ts +52 -0
  511. package/dist/state/paused-work.js +105 -0
  512. package/dist/state/plans.d.ts +27 -8
  513. package/dist/state/plans.js +14 -11
  514. package/dist/state/purge-analysis.d.ts +6 -6
  515. package/dist/state/purge-analysis.js +10 -2
  516. package/dist/state/report-session-state.d.ts +43 -18
  517. package/dist/state/report-session-state.js +43 -4
  518. package/dist/state/report-versions.d.ts +16 -11
  519. package/dist/state/report-versions.js +5 -3
  520. package/dist/state/runs.d.ts +70 -25
  521. package/dist/state/runs.js +127 -64
  522. package/dist/state/schema.d.ts +18 -21
  523. package/dist/state/schema.js +4 -5
  524. package/dist/state/snapshot-parse.d.ts +2 -2
  525. package/dist/state/snapshot-parse.js +14 -8
  526. package/dist/state/step-executions.d.ts +29 -8
  527. package/dist/state/step-executions.js +28 -4
  528. package/dist/state/tool-outputs.d.ts +2 -2
  529. package/dist/state/tool-outputs.js +2 -1
  530. package/dist/state/vector-store.d.ts +16 -16
  531. package/dist/state/vector-store.js +11 -7
  532. package/dist/tasks/data-profile-absorb.d.ts +3 -3
  533. package/dist/tasks/data-profile-absorb.js +21 -16
  534. package/dist/tasks/data-profile-activity.d.ts +5 -5
  535. package/dist/tasks/data-profile-activity.js +5 -5
  536. package/dist/tasks/data-profile-index.d.ts +4 -4
  537. package/dist/tasks/data-profile-index.js +32 -17
  538. package/dist/tasks/data-profile-resolve.d.ts +8 -8
  539. package/dist/tasks/data-profile-resolve.js +88 -62
  540. package/dist/tasks/data-profile.d.ts +40 -31
  541. package/dist/tasks/data-profile.js +138 -95
  542. package/dist/tasks/extract-values.d.ts +26 -79
  543. package/dist/tasks/extract-values.js +48 -108
  544. package/dist/tasks/refresh-regulatory-corpus.d.ts +4 -20
  545. package/dist/tasks/refresh-regulatory-corpus.js +12 -8
  546. package/dist/tasks/regulatory-corpus-boot-refresh.d.ts +2 -2
  547. package/dist/tasks/regulatory-corpus-boot-refresh.js +12 -10
  548. package/dist/testing.d.ts +60 -0
  549. package/dist/testing.js +96 -0
  550. package/dist/tools/ad-hoc-router.d.ts +11 -9
  551. package/dist/tools/ad-hoc-router.js +80 -51
  552. package/dist/tools/analysis-invocation.d.ts +7 -0
  553. package/dist/tools/analysis-invocation.js +29 -5
  554. package/dist/tools/approval/contract.d.ts +8 -52
  555. package/dist/tools/approval/contract.js +7 -36
  556. package/dist/tools/approval/gateway.d.ts +27 -28
  557. package/dist/tools/approval/gateway.js +33 -130
  558. package/dist/tools/approval/queries.d.ts +6 -11
  559. package/dist/tools/approval/queries.js +43 -29
  560. package/dist/tools/approval/uuidv7.js +2 -2
  561. package/dist/tools/bio/chembl.d.ts +3 -3
  562. package/dist/tools/bio/chembl.js +10 -0
  563. package/dist/tools/bio/comptox.d.ts +25 -8
  564. package/dist/tools/bio/comptox.js +4 -2
  565. package/dist/tools/bio/drug-gene-interactions.d.ts +27 -17
  566. package/dist/tools/bio/drug-gene-interactions.js +11 -8
  567. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -15
  568. package/dist/tools/bio/gene-disease-evidence.js +10 -6
  569. package/dist/tools/bio/gene-preclinical-profile.d.ts +11 -11
  570. package/dist/tools/bio/keys.d.ts +2 -2
  571. package/dist/tools/bio/keys.js +3 -3
  572. package/dist/tools/bio/lookup-annotation.d.ts +5 -5
  573. package/dist/tools/bio/lookup-annotation.js +3 -0
  574. package/dist/tools/bio/opentargets.d.ts +1 -1
  575. package/dist/tools/bio/opentargets.js +6 -0
  576. package/dist/tools/bio/pubchem.d.ts +2 -2
  577. package/dist/tools/bio/pubchem.js +6 -0
  578. package/dist/tools/bio/pubmed.d.ts +26 -16
  579. package/dist/tools/bio/pubmed.js +9 -3
  580. package/dist/tools/bio/search-clinical-trials.d.ts +4 -4
  581. package/dist/tools/bio/search-clinical-trials.js +5 -1
  582. package/dist/tools/bio/search-faers.d.ts +2 -2
  583. package/dist/tools/bio/search-faers.js +1 -1
  584. package/dist/tools/bio/search-gene.d.ts +2 -2
  585. package/dist/tools/bio/search-gene.js +1 -0
  586. package/dist/tools/bio/search-geo-datasets.d.ts +4 -4
  587. package/dist/tools/bio/search-geo-datasets.js +5 -5
  588. package/dist/tools/bio/search-interactions.d.ts +1 -1
  589. package/dist/tools/bio/search-patents.d.ts +28 -22
  590. package/dist/tools/bio/search-patents.js +20 -17
  591. package/dist/tools/bio/search-protein.d.ts +2 -2
  592. package/dist/tools/bio/search-protein.js +1 -1
  593. package/dist/tools/bio/search-regulatory-approvals.d.ts +64 -18
  594. package/dist/tools/bio/search-regulatory-approvals.js +21 -16
  595. package/dist/tools/bio/target-safety.d.ts +8 -8
  596. package/dist/tools/bio/target-safety.js +11 -6
  597. package/dist/tools/capability-data.d.ts +40 -0
  598. package/dist/tools/capability-data.js +74 -0
  599. package/dist/tools/define-tool.d.ts +55 -46
  600. package/dist/tools/define-tool.js +39 -27
  601. package/dist/tools/display/normalize-echart-spec.d.ts +2 -2
  602. package/dist/tools/display/normalize-echart-spec.js +51 -27
  603. package/dist/tools/display/show-user.d.ts +2 -2
  604. package/dist/tools/display/show-user.js +1 -1
  605. package/dist/tools/execute-analysis.d.ts +54 -17
  606. package/dist/tools/execute-analysis.js +193 -62
  607. package/dist/tools/knowledge/check.d.ts +182 -0
  608. package/dist/tools/knowledge/check.js +94 -0
  609. package/dist/tools/knowledge/client.d.ts +351 -0
  610. package/dist/tools/knowledge/client.js +319 -0
  611. package/dist/tools/knowledge/environment.d.ts +78 -0
  612. package/dist/tools/knowledge/environment.js +116 -0
  613. package/dist/tools/knowledge/index.d.ts +49 -0
  614. package/dist/tools/knowledge/index.js +70 -0
  615. package/dist/tools/knowledge/local-slots.d.ts +62 -0
  616. package/dist/tools/knowledge/local-slots.js +184 -0
  617. package/dist/tools/knowledge/pin.d.ts +14 -0
  618. package/dist/tools/knowledge/pin.js +17 -0
  619. package/dist/tools/knowledge/recommend.d.ts +67 -0
  620. package/dist/tools/knowledge/recommend.js +106 -0
  621. package/dist/tools/knowledge/situation.d.ts +143 -0
  622. package/dist/tools/knowledge/situation.js +157 -0
  623. package/dist/tools/knowledge/skeleton.d.ts +69 -0
  624. package/dist/tools/knowledge/skeleton.js +201 -0
  625. package/dist/tools/knowledge/template.d.ts +139 -0
  626. package/dist/tools/knowledge/template.js +273 -0
  627. package/dist/tools/lib/alphafold-client.js +2 -1
  628. package/dist/tools/lib/amass-client.d.ts +28 -28
  629. package/dist/tools/lib/amass-client.js +20 -10
  630. package/dist/tools/lib/api-utils.d.ts +13 -3
  631. package/dist/tools/lib/api-utils.js +17 -3
  632. package/dist/tools/lib/bgee-client.d.ts +6 -6
  633. package/dist/tools/lib/bgee-client.js +16 -10
  634. package/dist/tools/lib/cbioportal-client.d.ts +4 -4
  635. package/dist/tools/lib/cbioportal-client.js +9 -8
  636. package/dist/tools/lib/chembl-client.d.ts +14 -14
  637. package/dist/tools/lib/chembl-client.js +11 -4
  638. package/dist/tools/lib/clinical-benchmarks-client.d.ts +4 -4
  639. package/dist/tools/lib/clinical-benchmarks-client.js +4 -7
  640. package/dist/tools/lib/clinical-trials-client.d.ts +18 -18
  641. package/dist/tools/lib/clinical-trials-client.js +10 -10
  642. package/dist/tools/lib/clinvar-client.d.ts +2 -2
  643. package/dist/tools/lib/clinvar-client.js +6 -6
  644. package/dist/tools/lib/ctx-ops.d.ts +23 -23
  645. package/dist/tools/lib/ctx-ops.js +13 -10
  646. package/dist/tools/lib/dgidb-client.d.ts +6 -6
  647. package/dist/tools/lib/dgidb-client.js +10 -12
  648. package/dist/tools/lib/disgenet-client.d.ts +4 -4
  649. package/dist/tools/lib/ema-client.d.ts +2 -2
  650. package/dist/tools/lib/ensembl-client.d.ts +4 -4
  651. package/dist/tools/lib/ensembl-client.js +5 -4
  652. package/dist/tools/lib/gwas-catalog-client.d.ts +4 -4
  653. package/dist/tools/lib/gwas-catalog-client.js +17 -17
  654. package/dist/tools/lib/html-text.d.ts +6 -0
  655. package/dist/tools/lib/html-text.js +9 -0
  656. package/dist/tools/lib/identifier-resolver.d.ts +4 -4
  657. package/dist/tools/lib/identifier-resolver.js +7 -3
  658. package/dist/tools/lib/impc-client.d.ts +8 -8
  659. package/dist/tools/lib/impc-client.js +46 -35
  660. package/dist/tools/lib/iuphar-client.d.ts +2 -2
  661. package/dist/tools/lib/iuphar-client.js +7 -16
  662. package/dist/tools/lib/monarch-client.d.ts +4 -4
  663. package/dist/tools/lib/openfda-client.d.ts +22 -22
  664. package/dist/tools/lib/openfda-client.js +23 -22
  665. package/dist/tools/lib/opentargets-client.d.ts +12 -12
  666. package/dist/tools/lib/pathway-client.d.ts +4 -4
  667. package/dist/tools/lib/pathway-client.js +15 -19
  668. package/dist/tools/lib/pharmgkb-client.d.ts +2 -2
  669. package/dist/tools/lib/pharmgkb-client.js +1 -1
  670. package/dist/tools/lib/protein-family-modality.d.ts +2 -2
  671. package/dist/tools/lib/protein-family-modality.js +0 -2
  672. package/dist/tools/lib/protein-family-normalize.js +7 -0
  673. package/dist/tools/lib/pubchem-client.d.ts +2 -2
  674. package/dist/tools/lib/pubchem-client.js +1 -0
  675. package/dist/tools/lib/pubchem-ops.d.ts +4 -4
  676. package/dist/tools/lib/pubchem-ops.js +8 -0
  677. package/dist/tools/lib/pubmed-client.d.ts +9 -9
  678. package/dist/tools/lib/pubmed-client.js +1 -1
  679. package/dist/tools/lib/string-client.d.ts +6 -6
  680. package/dist/tools/lib/uniprot-client.d.ts +8 -8
  681. package/dist/tools/lib/uniprot-client.js +5 -1
  682. package/dist/tools/memory/update-working-memory.d.ts +16 -13
  683. package/dist/tools/memory/update-working-memory.js +21 -3
  684. package/dist/tools/read-tool-output.js +8 -2
  685. package/dist/tools/registry.d.ts +2 -2
  686. package/dist/tools/report-authoring/authoring-tools.d.ts +54 -23
  687. package/dist/tools/report-authoring/authoring-tools.js +128 -62
  688. package/dist/tools/report-session/derive-table.d.ts +19 -59
  689. package/dist/tools/report-session/derive-table.js +120 -122
  690. package/dist/tools/report-session/examine-page.d.ts +6 -6
  691. package/dist/tools/report-session/examine-page.js +14 -12
  692. package/dist/tools/report-session/list-artifacts.d.ts +6 -6
  693. package/dist/tools/report-session/list-artifacts.js +5 -5
  694. package/dist/tools/report-session/page-faults.d.ts +6 -6
  695. package/dist/tools/report-session/page-faults.js +3 -1
  696. package/dist/tools/report-session/preview-report.d.ts +4 -3
  697. package/dist/tools/report-session/preview-report.js +26 -7
  698. package/dist/tools/report-session/record-version.d.ts +9 -4
  699. package/dist/tools/report-session/record-version.js +16 -9
  700. package/dist/tools/report-session/session-page-publisher.d.ts +6 -6
  701. package/dist/tools/research/generate-analogy-report.d.ts +14 -7
  702. package/dist/tools/research/generate-analogy-report.js +80 -76
  703. package/dist/tools/research/generate-plan.d.ts +39 -16
  704. package/dist/tools/research/generate-plan.js +565 -338
  705. package/dist/tools/research/guarded-address.d.ts +20 -0
  706. package/dist/tools/research/guarded-address.js +149 -0
  707. package/dist/tools/research/inspect-data-profile.d.ts +27 -20
  708. package/dist/tools/research/inspect-data-profile.js +26 -25
  709. package/dist/tools/research/inspect-run.d.ts +54 -27
  710. package/dist/tools/research/inspect-run.js +50 -40
  711. package/dist/tools/research/literature-reviewer.d.ts +8 -7
  712. package/dist/tools/research/literature-reviewer.js +29 -28
  713. package/dist/tools/research/search-arxiv.d.ts +1 -1
  714. package/dist/tools/research/search-arxiv.js +1 -1
  715. package/dist/tools/research/search-github-repos.d.ts +10 -7
  716. package/dist/tools/research/search-github-repos.js +25 -22
  717. package/dist/tools/research/search-semantic-scholar.js +1 -1
  718. package/dist/tools/research/searxng-engine.d.ts +40 -0
  719. package/dist/tools/research/searxng-engine.js +122 -0
  720. package/dist/tools/research/web-fetch.d.ts +127 -0
  721. package/dist/tools/research/web-fetch.js +618 -0
  722. package/dist/tools/research/web-search.d.ts +84 -0
  723. package/dist/tools/research/web-search.js +117 -0
  724. package/dist/tools/sandbox/link-packages.d.ts +11 -7
  725. package/dist/tools/sandbox/link-packages.js +11 -8
  726. package/dist/tools/sandbox/list-available-packages.d.ts +24 -12
  727. package/dist/tools/sandbox/list-available-packages.js +43 -29
  728. package/dist/tools/sandbox/list-available-refs.d.ts +38 -14
  729. package/dist/tools/sandbox/list-available-refs.js +119 -37
  730. package/dist/tools/sandbox/report-blocker.d.ts +30 -37
  731. package/dist/tools/sandbox/report-blocker.js +22 -68
  732. package/dist/tools/sandbox/skills.d.ts +16 -12
  733. package/dist/tools/sandbox/skills.js +16 -12
  734. package/dist/tools/sandbox/submit-file-metadata.d.ts +12 -9
  735. package/dist/tools/sandbox/submit-file-metadata.js +18 -21
  736. package/dist/tools/start-report-session.d.ts +13 -5
  737. package/dist/tools/start-report-session.js +35 -8
  738. package/dist/tools/terminal-text.d.ts +14 -0
  739. package/dist/tools/terminal-text.js +51 -0
  740. package/dist/tools/workspace/decision-record.d.ts +81 -0
  741. package/dist/tools/workspace/decision-record.js +127 -0
  742. package/dist/tools/workspace/edit-file.d.ts +59 -18
  743. package/dist/tools/workspace/edit-file.js +98 -15
  744. package/dist/tools/workspace/execute-command.d.ts +31 -12
  745. package/dist/tools/workspace/execute-command.js +37 -8
  746. package/dist/tools/workspace/file-stat.d.ts +6 -3
  747. package/dist/tools/workspace/grep.d.ts +10 -7
  748. package/dist/tools/workspace/grep.js +9 -3
  749. package/dist/tools/workspace/index.d.ts +1 -0
  750. package/dist/tools/workspace/index.js +1 -0
  751. package/dist/tools/workspace/list-files.d.ts +6 -3
  752. package/dist/tools/workspace/mutator.d.ts +28 -15
  753. package/dist/tools/workspace/mutator.js +107 -53
  754. package/dist/tools/workspace/read-file.d.ts +8 -5
  755. package/dist/tools/workspace/result-bounds.d.ts +5 -5
  756. package/dist/tools/workspace/result-bounds.js +4 -2
  757. package/dist/tools/workspace/run-exec.d.ts +2 -2
  758. package/dist/tools/workspace/run-exec.js +3 -3
  759. package/dist/tools/workspace/scan-inputs.d.ts +8 -5
  760. package/dist/tools/workspace/scan-inputs.js +17 -5
  761. package/dist/tools/workspace/show-file.d.ts +1 -1
  762. package/dist/tools/workspace/show-file.js +2 -1
  763. package/dist/tools/workspace/show-plan.d.ts +8 -5
  764. package/dist/tools/workspace/show-plan.js +9 -8
  765. package/dist/tools/workspace/workspace-search.d.ts +41 -13
  766. package/dist/tools/workspace/workspace-search.js +27 -14
  767. package/dist/tools/workspace/write-file.d.ts +9 -7
  768. package/dist/workflows/execute-analysis-scheduler.d.ts +9 -9
  769. package/dist/workflows/execute-analysis-scheduler.js +6 -3
  770. package/dist/workflows/execute-analysis.d.ts +100 -76
  771. package/dist/workflows/execute-analysis.js +380 -247
  772. package/dist/workflows/pause.d.ts +71 -0
  773. package/dist/workflows/pause.js +173 -0
  774. package/dist/workflows/sandbox-step.d.ts +50 -61
  775. package/dist/workflows/sandbox-step.js +99 -106
  776. package/dist/workflows/test/sql-writes.d.ts +21 -0
  777. package/dist/workflows/test/sql-writes.js +36 -0
  778. package/dist/workspace/filesystem.d.ts +6 -6
  779. package/dist/workspace/filesystem.js +10 -8
  780. package/dist/workspace/paths.d.ts +1 -1
  781. package/dist/workspace/paths.js +15 -4
  782. package/dist/workspace/search-config.d.ts +2 -2
  783. package/dist/workspace/search-config.js +1 -1
  784. package/package.json +25 -22
  785. package/dist/loop/continue-agent.d.ts +0 -40
  786. package/dist/loop/continue-agent.js +0 -38
  787. package/dist/loop/genai-spans.d.ts +0 -47
  788. package/dist/loop/genai-spans.js +0 -134
  789. package/dist/loop/run-agent.d.ts +0 -236
  790. package/dist/loop/run-agent.js +0 -1110
  791. package/dist/loop/run-step.d.ts +0 -29
  792. package/dist/loop/run-step.js +0 -34
  793. package/dist/loop/run-to-terminal.d.ts +0 -66
  794. package/dist/loop/run-to-terminal.js +0 -90
  795. package/dist/loop/tool-mask.d.ts +0 -23
  796. package/dist/loop/tool-mask.js +0 -46
  797. package/dist/providers/streaming-chat.d.ts +0 -32
  798. package/dist/providers/streaming-chat.js +0 -91
  799. package/dist/tasks/derive-table-exec.d.ts +0 -54
  800. package/dist/tasks/derive-table-exec.js +0 -86
  801. package/dist/workflows/suspension.d.ts +0 -32
  802. package/dist/workflows/suspension.js +0 -38
@@ -39,69 +39,72 @@
39
39
  * `partial`).
40
40
  * - the halt cascade (cancel in-flight via
41
41
  * `Promise.allSettled(handles.map(h => DBOS.cancelWorkflow(h.workflowID)))`,
42
- * stop scheduling) survives only for a suspension of a child and the
43
- * `neverFits` plan-validation guard.
44
- * 4. Suspension cascade (workflow-suspension spec)
45
- * - a child that suspends sends a typed message on `CHILD_SUSPENDED_TOPIC`
46
- * and cancels itself. The parent reads the kind of the message, never a
47
- * reason string: it cancels the siblings, then self-cancels to
48
- * `CANCELLED` (NOT `ERROR` — ERROR isn't resumable; NOTES #3)
49
- * - a refusal of the `RunCharge.open` gate with the suspend flag suspends
50
- * the run before any step starts
51
- * - a `suspend` error of a synthesis model request suspends the run in
52
- * place of the synthesis failure
53
- * - the run row and the analysis flip to `suspended_insufficient_funds` in
54
- * `collectAndComplete`. The run status is terminal and does not block a
55
- * new run of the plan. The paused parent lands in `CANCELLED`, a
56
- * durably reschedulable state; re-driving it is a future enhancement,
57
- * not yet wired here. The reason of the host rides to the run row, the
58
- * charge close, the metric, and the terminal part, unread.
42
+ * stop scheduling) survives only for the `neverFits` plan-validation guard.
43
+ * 4. Pause (work-pause spec)
44
+ * - a step pauses in its own workflow, and the parent waits for its result as
45
+ * for a step that runs. The other steps go on, and the run row stays
46
+ * `running`
47
+ * - a refusal of the `RunCharge.open` gate with the suspend flag pauses the
48
+ * run before any step starts, and the charge opens again after the resume
49
+ * - a failed model request of the synthesis with a cause of a pause waits in
50
+ * the loop of the synthesizer
59
51
  * 5. `synthesizeFindings` (single sequential block, no sandbox)
60
52
  * 6. `collectAndComplete` (terminal — runs on ALL paths)
61
53
  * - determine final status from completed/cancelled/failed counts
62
- * - update `cortex_runs.status` + `error`, and inside that same step record
63
- * the run outcome metric plus the outcome of every settled step whose
64
- * child never wrote its own terminal row (a cancelled child, or one that
65
- * died before `failStep`)
66
- * - close running charge with matching reason
67
- * - revoke run authorization via the injected `runAuthorizer` seam
54
+ * - update `cortex_runs.status` + `error`. When the write changes the row,
55
+ * the same step emits `run_completed` before the commit, records the run
56
+ * outcome metric plus the outcome of every settled step whose child never
57
+ * wrote its own terminal row (a cancelled child, or one that died before
58
+ * `failStep`), closes the running charge with the matching outcome, and
59
+ * revokes the run authorization through the injected `runAuthorizer`
60
+ * - the write retries with backoff on a failed transaction. When the write is
61
+ * refused, or fails on each attempt, close and revoke in their own steps, then
62
+ * emit `run_completed`. After a failed write the row stays `running`, so the
63
+ * body records its run end as a workflow event for the run canceler
68
64
  * - emit terminal stream part
69
65
  * - WRITES NOTHING to the conversation thread (results are pull-only
70
66
  * via `inspectRun`)
71
67
  */
72
- import { DBOS, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
68
+ import { errorMessageOf } from "../lib/thrown.js";
69
+ import { textOf } from "../lib/unknown-text.js";
70
+ import { DBOS } from "@dbos-inc/dbos-sdk";
71
+ import { isWorkflowCancellation } from "../runtime/dbos.js";
73
72
  import { err, ok } from "neverthrow";
74
73
  import { mkdir } from "node:fs/promises";
75
74
  import { join } from "node:path";
76
75
  import { forStep } from "../auth/types.js";
76
+ import { RUN_END_EVENT_KEY } from "../execution/run-end-record.js";
77
77
  import { createNoopLogger } from "../lib/console-logger.js";
78
78
  import { deliverNotice, passGate } from "../lib/hooks.js";
79
79
  import { ATTR_INFLEXA_STEP_ID, stableSpan } from "../lib/otel-spans.js";
80
80
  import { recordRunCompleted, recordStepCompleted, stepOutcomeOf } from "../lib/metrics.js";
81
- import { describeDbError } from "../lib/db-result.js";
81
+ import { describeDbError, withTransaction } from "../lib/db-result.js";
82
82
  import { unwrapOrThrow } from "../lib/result.js";
83
- import { countArtifactsByRuns, insertStepExecution, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, queryStepsByRun, seedStepExecutions, setRunSynthesisOutcome, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, updateStepExecution, } from "../state/index.js";
83
+ import { countArtifactsByRuns, insertStepExecution, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, queryStepsByRun, seedStepExecutions, setRunSynthesisOutcome, sweepPendingStepExecutions, updateRunStatus, updateStepExecution, } from "../state/index.js";
84
+ import { clearPausesOfWork } from "../state/paused-work.js";
84
85
  import { addChatUsage, hasReportedUsage } from "../loop/metrics.js";
85
86
  import { flattenDataPart } from "../contracts/chat-frame.js";
86
87
  import { SYNTHESIS_AGENT_ID, loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
87
- import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory } from "../prompts/briefing.js";
88
+ import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory, } from "../prompts/briefing.js";
88
89
  import { SYNTHESIS_STEP_ID, runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
89
90
  import { isChatDataPart, synthesisEmission } from "../sandbox/sandbox-step-translate.js";
90
91
  import { synthesizeRun } from "../app/synthesize-run.js";
91
92
  import { computeTopologicalLevels, scheduleReady, validatePlanDag } from "./execute-analysis-scheduler.js";
92
93
  import { recordCancelledChild } from "./metrics.js";
93
- import { CHILD_SUSPENDED_TOPIC } from "./sandbox-step.js";
94
- import { cancelSelf, suspensionOfFailure, suspensionOfRefusal } from "./suspension.js";
94
+ import { pauseAtGate, workPause } from "./pause.js";
95
+ import { presentGet } from "../lib/present.js";
95
96
  // ── In-body orchestration helpers ────────────────────────────────────
96
97
  /** Emit a chat data part on the parent's DBOS stream. */
97
98
  function emitStreamPart(part) {
98
99
  return DBOS.writeStream("events", part);
99
100
  }
100
101
  /**
101
- * A failed write rolls back no side effect that succeeded and never fails the
102
- * run. A DBOS exception passes through with no change.
102
+ * A failed write rolls back no side effect that succeeded. The step body logs
103
+ * its own failure and never throws. A DBOS exception passes through with no
104
+ * change.
103
105
  */
104
106
  async function ledgerStep(log, name, write) {
107
+ // typecheck-disable-next-line must-use-result -- a ledger miss must not fail the run
105
108
  await DBOS.runStep(async () => {
106
109
  const written = await write();
107
110
  if (written.isOk())
@@ -151,8 +154,8 @@ function buildRunObservation(args) {
151
154
  agent: input.agentByStepId[s.id] ?? "unknown",
152
155
  // A step absent from the map has not been reached yet; `pending` is its honest state.
153
156
  status: rt?.status ?? "pending",
154
- ...(rt?.durationMs !== undefined ? { durationMs: rt.durationMs } : {}),
155
- ...(rt?.error !== undefined ? { error: rt.error } : {}),
157
+ durationMs: rt?.durationMs,
158
+ error: rt?.error,
156
159
  };
157
160
  }),
158
161
  };
@@ -183,7 +186,7 @@ function observeRunGuarded(deps, observation) {
183
186
  * projection/timeout-propagation tests.
184
187
  */
185
188
  export function buildChildInput(args) {
186
- const { input, stepId, level, runId, workflowId, prompt } = args;
189
+ const { input, stepId, level, runId, prompt, templateBinding } = args;
187
190
  const resources = input.resourcesByStepId[stepId];
188
191
  if (!resources) {
189
192
  throw new Error(`executeAnalysis: step "${stepId}" missing from resourcesByStepId — every step must declare resources`);
@@ -205,9 +208,11 @@ export function buildChildInput(args) {
205
208
  // genuinely declared nothing, and it silently deletes every same-run edge
206
209
  // the step was entitled to.
207
210
  dependsOn: planStep.depends_on,
211
+ // Composed beside the seed, thus the same checkpoint carries both and a
212
+ // replay hands the child the binding its briefing describes.
213
+ templateBinding,
208
214
  level,
209
215
  prompt,
210
- parentWorkflowId: workflowId,
211
216
  resources,
212
217
  timeoutSeconds: input.timeoutByStepId?.[stepId],
213
218
  };
@@ -222,13 +227,18 @@ export function buildChildInput(args) {
222
227
  * rendered any earlier (at plan submission, say) cannot mention them, and the
223
228
  * agent is left to rediscover its own upstream.
224
229
  *
225
- * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
226
- * checkpointed and a replay returns it verbatim — the DB and disk are not read
227
- * again and cannot drift the seed under a recovered run. Never call it
228
- * unwrapped from the workflow body.
230
+ * The template contract of a grounded step is read with the package, and the
231
+ * plan settings are bound to it: the seed describes the binding, and the child
232
+ * input carries it, from one checkpoint. The child fetches nothing.
233
+ *
234
+ * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed seed is
235
+ * checkpointed and a replay returns it verbatim — the DB, the disk, and the
236
+ * knowledge service are not read again and cannot drift the seed under a
237
+ * recovered run. Never call it unwrapped from the workflow body.
229
238
  */
230
239
  export async function composeStepSeed(args) {
231
- return composeStepBriefing(await gatherStepPackage(args));
240
+ const { briefing, templateBinding } = await gatherStepPackage(args);
241
+ return { prompt: composeStepBriefing(briefing), templateBinding };
232
242
  }
233
243
  /**
234
244
  * Do every read of one step's package and give the data its seed is rendered
@@ -242,6 +252,9 @@ export async function composeStepSeed(args) {
242
252
  * so an edit during a run reaches each later step, and two steps of one run
243
253
  * can carry different copies.
244
254
  *
255
+ * The contract of the template a grounded step renders is read here too, from
256
+ * the knowledge client, and the plan settings are bound to it.
257
+ *
245
258
  * A read failure throws (house rules): a swallowed error would be frozen into
246
259
  * the checkpointed seed and served to every replay of this step.
247
260
  */
@@ -282,12 +295,149 @@ export async function gatherStepPackage(args) {
282
295
  agentByStepId: input.agentByStepId,
283
296
  pool: deps.pool,
284
297
  });
298
+ const contract = await resolveTemplateContract(step, deps.knowledge);
299
+ return {
300
+ briefing: {
301
+ step,
302
+ workspace,
303
+ profile: profile?.result ?? null,
304
+ upstream,
305
+ memory: { goal: memory.goal, constraints: memory.constraints },
306
+ ...contract.briefing,
307
+ },
308
+ templateBinding: contract.binding,
309
+ };
310
+ }
311
+ /** A served contract carries no `match`; the two refusals do. */
312
+ function isTemplateContract(answer) {
313
+ return !("match" in answer);
314
+ }
315
+ /**
316
+ * Fetch the contract of the template a step grounds on, and bind the plan
317
+ * settings to it. A step without a template has no template part. A step
318
+ * with one and no way to read it (no client, no answer, or an unknown
319
+ * template) gets the reason, thus the seed reports the absence rather than
320
+ * hiding it, and no binding.
321
+ */
322
+ async function resolveTemplateContract(step, client) {
323
+ const ref = step.grounding?.template;
324
+ if (!ref)
325
+ return { briefing: {} };
326
+ if (!client)
327
+ return { briefing: { templateNotRetrieved: "no knowledge client is bound" } };
328
+ const answer = await client.contract(ref);
329
+ if (!isTemplateContract(answer)) {
330
+ const reason = answer.match === "unavailable"
331
+ ? `the knowledge service did not answer: ${answer.reason}`
332
+ : `the knowledge service refused the lookup: ${answer.message}`;
333
+ return { briefing: { templateNotRetrieved: reason } };
334
+ }
335
+ return bindTemplateContract(ref, answer, step.grounding?.settings ?? [], {
336
+ stepId: step.id,
337
+ claims: step.grounding?.claims ?? [],
338
+ snapshot: step.grounding?.snapshot && step.grounding.snapshot !== "none" ? step.grounding.snapshot : undefined,
339
+ });
340
+ }
341
+ /** The version of a template reference (`tpl-x@1.0.0` gives `1.0.0`), or `undefined` when the reference has none. */
342
+ function versionOfRef(ref) {
343
+ const at = ref.indexOf("@");
344
+ return at < 0 ? undefined : ref.slice(at + 1);
345
+ }
346
+ /** Two setting values agree when their JSON agrees: the values are scalars or lists of strings. */
347
+ function sameSettingValue(a, b) {
348
+ return JSON.stringify(a) === JSON.stringify(b);
349
+ }
350
+ /**
351
+ * Whether a plan value is a value of the slot, and not a policy word. A
352
+ * procedure parameter and a template slot share a name, but not always a
353
+ * value domain: `min_samples = smallest_group_size` names a rule the agent
354
+ * applies, and the slot takes the resulting integer. Only a member of the
355
+ * enum of the slot, a number for a number slot, or a boolean for a boolean
356
+ * slot binds. A free-text slot never binds, because the host cannot tell a
357
+ * policy word from a value there.
358
+ */
359
+ function fitsSlot(slot, value) {
360
+ if (slot.enum)
361
+ return typeof value === "string" && slot.enum.includes(value);
362
+ if (slot.type === "integer" || slot.type === "number")
363
+ return typeof value === "number";
364
+ if (slot.type === "boolean")
365
+ return typeof value === "boolean";
366
+ return false;
367
+ }
368
+ function bindTemplateContract(ref, contract, settings, grounding) {
369
+ const refVersion = versionOfRef(ref);
370
+ const versionMatches = refVersion === undefined || refVersion === contract.version;
371
+ const adaptable = contract.parameters.filter((slot) => slot.adaptable);
372
+ const adaptableNames = new Set(adaptable.map((slot) => slot.name));
373
+ const pinnedByName = new Map(contract.parameters.filter((slot) => !slot.adaptable).map((slot) => [slot.name, slot]));
374
+ const bound = [];
375
+ const unbound = [];
376
+ const slots = {};
377
+ const sources = {};
378
+ for (const setting of settings) {
379
+ const shown = `\`${setting.name}\` = ${JSON.stringify(setting.value)} (${setting.step})`;
380
+ if (!versionMatches) {
381
+ unbound.push(`${shown}: not bound, because the served version differs from the plan`);
382
+ continue;
383
+ }
384
+ if (adaptableNames.has(setting.name)) {
385
+ const slot = adaptable.find((candidate) => candidate.name === setting.name);
386
+ if (slot && !fitsSlot(slot, setting.value)) {
387
+ unbound.push(`${shown}: a policy of the plan, not a value of the \`${setting.name}\` slot (${slot.type}); state the value it gives`);
388
+ continue;
389
+ }
390
+ const prior = slots[setting.name];
391
+ if (prior !== undefined) {
392
+ if (!sameSettingValue(prior, setting.value))
393
+ unbound.push(`${shown}: differs from the bound value ${JSON.stringify(prior)} of the same slot`);
394
+ continue;
395
+ }
396
+ slots[setting.name] = setting.value;
397
+ if (setting.source)
398
+ sources[setting.name] = setting.source;
399
+ bound.push({ name: setting.name, value: setting.value, source: setting.source ?? "plan" });
400
+ continue;
401
+ }
402
+ const pinned = pinnedByName.get(setting.name);
403
+ if (pinned) {
404
+ const pinnedValue = pinned.default === undefined ? "a fixed value" : JSON.stringify(pinned.default);
405
+ unbound.push(sameSettingValue(pinned.default, setting.value)
406
+ ? `${shown}: pinned by the template at the same value`
407
+ : `${shown}: the template pins \`${setting.name}\` at ${pinnedValue}, and the plan value cannot be sent`);
408
+ continue;
409
+ }
410
+ unbound.push(`${shown}: no slot of the template carries it`);
411
+ }
285
412
  return {
286
- step,
287
- workspace,
288
- profile: profile?.result ?? null,
289
- upstream,
290
- memory: { goal: memory.goal, constraints: memory.constraints },
413
+ briefing: {
414
+ template: {
415
+ ref,
416
+ version_served: contract.version,
417
+ slots: adaptable,
418
+ inputs: (contract.inputs ?? []).map((input) => ({
419
+ name: input.name,
420
+ path: input.path,
421
+ description: input.description,
422
+ })),
423
+ bound,
424
+ unbound_settings: unbound,
425
+ },
426
+ },
427
+ binding: versionMatches
428
+ ? {
429
+ template: ref,
430
+ slots,
431
+ sources,
432
+ step: grounding.stepId,
433
+ claims: grounding.claims,
434
+ snapshot: grounding.snapshot,
435
+ // The local slots of the contract, thus the tool binds a path or a column name on the machine
436
+ // without a second read of the contract.
437
+ local: contract.parameters.filter((slot) => slot.local === true),
438
+ adaptable: adaptable.map((slot) => slot.name),
439
+ }
440
+ : undefined,
291
441
  };
292
442
  }
293
443
  /**
@@ -330,13 +480,10 @@ async function collectUpstreamHandoffs(args) {
330
480
  * its reason in `blockedReason` — an honest agent-declared blocker, not an
331
481
  * error — and a failure carries the same reason string
332
482
  * `persist-synthesis-outcome` writes to `cortex_runs.synthesis_reason`, so the
333
- * two ledgers cannot disagree about why synthesis died. A suspension is not an
334
- * outcome: the row is canceled, as the row of a suspended step. Exported for
335
- * the projection tests (the `buildChildInput` pattern).
483
+ * two ledgers cannot disagree about why synthesis died. Exported for the
484
+ * projection tests (the `buildChildInput` pattern).
336
485
  */
337
486
  export function synthesisRowUpdate(outcome, durationMs) {
338
- if ("kind" in outcome)
339
- return { status: "canceled", durationMs, error: outcome.reason };
340
487
  switch (outcome.status) {
341
488
  case "produced":
342
489
  return { status: "completed", durationMs };
@@ -355,9 +502,11 @@ export function synthesisRowUpdate(outcome, durationMs) {
355
502
  /**
356
503
  * Run-level literature-grounded synthesis (no sandbox). Returns the
357
504
  * quick-display findings for the run-completed card; empty when synthesis is
358
- * skipped or produced no synthesizable content. A genuine failure re-throws
359
- * inside the caller's `DBOS.runStep`, failing the run (D10). Exported so
360
- * integration tests can drive the wired synthesis without a full parent body.
505
+ * skipped or produced no synthesizable content. A genuine failure re-throws,
506
+ * failing the run (D10). It runs in the body of the run workflow: each read and
507
+ * write is a step, each model call is a step, and each reviewer call starts a
508
+ * child workflow. Exported so integration tests can drive the wired synthesis
509
+ * without a full parent body.
361
510
  */
362
511
  export function synthesizeFindings(args) {
363
512
  const { analysisId, runId, completedSteps, session, deps } = args;
@@ -388,10 +537,9 @@ export function synthesizeFindings(args) {
388
537
  embedding: deps.embedding,
389
538
  resolveWorkspaceRoot: deps.resolveWorkspaceRoot,
390
539
  synthesisModel: deps.synthesisModel,
391
- bioKeys: deps.bioKeys,
392
- citationResolver: deps.citationResolver,
540
+ literatureReviewer: deps.literatureReviewer,
393
541
  usageRecorder: deps.usageRecorder,
394
- ...(deps.toolOutputStore ? { toolOutputStore: deps.toolOutputStore } : {}),
542
+ toolOutputStore: deps.toolOutputStore,
395
543
  }, {
396
544
  analysisId,
397
545
  runId,
@@ -408,9 +556,10 @@ export function synthesizeFindings(args) {
408
556
  // call path.
409
557
  session: forStep(session, SYNTHESIS_STEP_ID),
410
558
  emit,
559
+ pause: args.pause?.afterFailure,
560
+ durably: (name, run) => DBOS.runStep(run, { name }),
411
561
  onProgress: async (phase, activity, extra = {}) => {
412
- // Synthesis runs inside one `DBOS.runStep`, thus `DBOS.now()` reads the wall clock here, and a rerun of
413
- // the step after a crash writes its entries again under the same ids. A reader dedups an entry by id.
562
+ // The body checkpoints the clock and each write, thus a replay writes no entry again.
414
563
  const seq = progressSeq++;
415
564
  const at = new Date(await DBOS.now()).toISOString();
416
565
  const parts = synthesisEmission(runId, SYNTHESIS_STEP_ID, seq, phase, activity, at, extra);
@@ -448,7 +597,17 @@ export async function runExecuteAnalysisBody(input, deps) {
448
597
  // the run authorization is already minted and rides in `input.runSession`.
449
598
  const runId = input.runSession.runFrame.runId;
450
599
  const workflowId = runId;
451
- const init = await validateAndInit(input, runId, deps);
600
+ const pause = workPause({
601
+ pool: deps.pool,
602
+ workId: runId,
603
+ workKind: "run",
604
+ analysisId: input.analysisId,
605
+ emit: emitStreamPart,
606
+ retryDelaysMs: deps.pauseRetryDelaysMs,
607
+ run: { session: input.runSession, authorizer: deps.runAuthorizer },
608
+ logger,
609
+ });
610
+ const init = await validateAndInit(input, runId, deps, pause);
452
611
  if (init.kind === "joined-existing") {
453
612
  return {
454
613
  runId: init.runId,
@@ -460,7 +619,6 @@ export async function runExecuteAnalysisBody(input, deps) {
460
619
  };
461
620
  }
462
621
  if (init.kind === "charge-refused") {
463
- const suspension = suspensionOfRefusal(init.refusal) ?? null;
464
622
  const refusedAtMs = await DBOS.now();
465
623
  const refused = await collectAndComplete({
466
624
  input,
@@ -470,16 +628,13 @@ export async function runExecuteAnalysisBody(input, deps) {
470
628
  completed: new Set(),
471
629
  failed: new Set(),
472
630
  canceled: new Set(),
473
- suspension,
474
631
  failureReason: init.refusal.reason,
475
- forceFailed: suspension === null,
632
+ forceFailed: true,
476
633
  findings: [],
477
634
  synthesisOutcome: null,
478
635
  deps,
479
636
  });
480
637
  observeRunGuarded(deps, buildRunObservation({ input, runId, status: refused.status, stepStates: new Map() }));
481
- if (suspension !== null)
482
- return cancelSelf("self-cancel-suspended");
483
638
  return refused;
484
639
  }
485
640
  // Seed the full DAG as `pending` rows so ledger readers see every step from
@@ -550,12 +705,11 @@ export async function runExecuteAnalysisBody(input, deps) {
550
705
  // still run the terminal block (close charge + revoke run authorization), and re-throw
551
706
  // after so the workflow record goes to ERROR.
552
707
  let synthesisError = null;
553
- let synthesisSuspension = null;
708
+ let synthesisMessage;
554
709
  let synthesisFindings = [];
555
710
  // Null when synthesis never ran (disabled or no completed steps): the ledger
556
711
  // columns stay NULL — "unknown", not a recorded skip — and the seeded
557
- // `synthesis` row (if any) stays `pending` for the terminal sweep. Also null
558
- // when synthesis suspended: a resume of the run gives the outcome.
712
+ // `synthesis` row (if any) stays `pending` for the terminal sweep.
559
713
  let synthesisOutcome = null;
560
714
  if (synthesisEnabled && final.completed.size > 0) {
561
715
  // Bracketing clock reads for the row's duration_ms — checkpointed, so a
@@ -575,13 +729,14 @@ export async function runExecuteAnalysisBody(input, deps) {
575
729
  }));
576
730
  let rowOutcome;
577
731
  try {
578
- const synthOut = await DBOS.runStep(() => synthesizeFindings({
732
+ const synthOut = await synthesizeFindings({
579
733
  analysisId: input.analysisId,
580
734
  runId,
581
735
  completedSteps: [...final.completed],
582
736
  session: input.runSession,
583
737
  deps,
584
- }), { name: "synthesize-findings" });
738
+ pause,
739
+ });
585
740
  synthesisFindings = synthOut.findings;
586
741
  synthesisOutcome = { status: synthOut.synthesisStatus, reason: synthOut.synthesisReason };
587
742
  rowOutcome = synthesisOutcome;
@@ -594,20 +749,14 @@ export async function runExecuteAnalysisBody(input, deps) {
594
749
  // this path either, so the run row is equally non-terminal and the pair
595
750
  // stays consistent — the pre-existing cancelled-mid-flight wedge shape
596
751
  // `inflexa run` already detects via its DBOS-status cross-check.
597
- if (err instanceof DBOSErrors.DBOSWorkflowCancelledError)
752
+ if (isWorkflowCancellation(err))
598
753
  throw err;
599
- const suspended = suspensionOfFailure(err);
600
- if (suspended !== undefined) {
601
- synthesisSuspension = suspended;
602
- rowOutcome = suspended;
603
- logger.warn("synthesizeFindings suspended — suspending the run", { runId, reason: suspended.reason });
604
- }
605
- else {
606
- synthesisError = err;
607
- synthesisOutcome = { status: "failed", reason: err instanceof Error ? err.message : String(err) };
608
- rowOutcome = synthesisOutcome;
609
- logger.error("synthesizeFindings failed — failing the run", { runId, ...logger.errorFields(err) });
610
- }
754
+ // A failure with a cause of a pause waited in the loop of the synthesizer, thus this one fails the run.
755
+ synthesisError = err;
756
+ synthesisMessage = errorMessageOf(err);
757
+ synthesisOutcome = { status: "failed", reason: errorMessageOf(err) ?? textOf(err) };
758
+ rowOutcome = synthesisOutcome;
759
+ logger.error("synthesizeFindings failed — failing the run", { runId, ...logger.errorFields(err) });
611
760
  }
612
761
  // Terminal stamp for the `synthesis` row — deliberately BEFORE
613
762
  // `collectAndComplete` writes the run row's terminal status, so no reader
@@ -627,7 +776,6 @@ export async function runExecuteAnalysisBody(input, deps) {
627
776
  // (6) collectAndComplete — terminal block. Runs on EVERY path. When synthesis
628
777
  // failed, force a failed terminal status so the charge/run-authorization close correctly
629
778
  // and the run row + stream report the failure before we re-throw.
630
- const suspension = final.suspension ?? synthesisSuspension;
631
779
  const result = await collectAndComplete({
632
780
  input,
633
781
  runId,
@@ -636,15 +784,8 @@ export async function runExecuteAnalysisBody(input, deps) {
636
784
  completed: final.completed,
637
785
  failed: final.failed,
638
786
  canceled: final.canceled,
639
- suspension,
640
- ...(final.usage ? { usage: final.usage } : {}),
641
- failureReason: synthesisError
642
- ? synthesisError instanceof Error
643
- ? `synthesis-failed: ${synthesisError.message}`
644
- : "synthesis-failed"
645
- : final.suspension === null && synthesisSuspension !== null
646
- ? synthesisSuspension.reason
647
- : final.failureReason,
787
+ usage: final.usage,
788
+ failureReason: synthesisError === null ? final.failureReason : synthesisMessage === undefined ? "synthesis-failed" : `synthesis-failed: ${synthesisMessage}`,
648
789
  forceFailed: synthesisError !== null,
649
790
  findings: synthesisFindings,
650
791
  synthesisOutcome,
@@ -652,20 +793,15 @@ export async function runExecuteAnalysisBody(input, deps) {
652
793
  });
653
794
  // Terminal snapshot, fired here rather than inside `collectAndComplete` because this is the
654
795
  // first point where BOTH the derived final status and the scheduler's final step map are in
655
- // hand. It precedes the suspension self-cancel below on purpose: that branch raises out of the
656
- // body, so an observer placed after it would never see a suspended run's last state.
796
+ // hand.
657
797
  observeRunGuarded(deps, buildRunObservation({ input, runId, status: result.status, stepStates: final.stepStates }));
658
- // Synthesis failure takes priority over the suspension cascade: a run whose
659
- // synthesis threw is definitively failed (ERROR), not a resumable
660
- // suspension. Only self-cancel when synthesis did not throw a failure.
661
- if (suspension !== null && synthesisError === null)
662
- return cancelSelf("self-cancel-suspended");
663
798
  // Re-throw after the terminal block so the workflow record goes to ERROR.
799
+ // oxlint-disable-next-line typescript/only-throw-error -- the caught synthesis failure rides as-is so the workflow record fails with the original cause; a wrapper would erase its identity.
664
800
  if (synthesisError !== null)
665
801
  throw synthesisError;
666
802
  return result;
667
803
  }
668
- async function validateAndInit(input, runId, deps) {
804
+ async function validateAndInit(input, runId, deps, pause) {
669
805
  // The cortex_runs row already exists (inserted by `executePlan`). Sanity-
670
806
  // check that no other active run for the same plan won the race — the
671
807
  // partial-unique index would have rejected the insert if so, so this is
@@ -682,11 +818,17 @@ async function validateAndInit(input, runId, deps) {
682
818
  recursive: true,
683
819
  });
684
820
  }, { name: "init-run-filesystem" });
685
- const opened = await DBOS.runStep(async () => await passGate("RunCharge.open", deps.runCharge.open({
821
+ const openCharge = () => DBOS.runStep(async () => await passGate("RunCharge.open", deps.runCharge.open({
686
822
  analysisId: input.analysisId,
687
823
  runId,
688
824
  session: input.runSession,
689
825
  })), { name: "open-running-charge" });
826
+ let opened = await openCharge();
827
+ // A refusal with the suspend flag waits in a pause, and the charge opens again after the resume.
828
+ while (opened.isErr() && opened.error.kind === "suspended") {
829
+ await pauseAtGate(pause, opened.error.reason);
830
+ opened = await openCharge();
831
+ }
690
832
  if (opened.isErr())
691
833
  return { kind: "charge-refused", refusal: opened.error };
692
834
  return { kind: "fresh" };
@@ -697,14 +839,13 @@ async function runSchedulerLoop(args) {
697
839
  const failed = new Set();
698
840
  const canceled = new Set();
699
841
  const inFlight = new Map();
700
- const childSuspensions = new Map();
701
842
  // Run-level usage aggregate, folded from the children's own durable results.
702
843
  // `getResult` IS the replay-safe source: a recovered parent reads back the
703
844
  // identical value from each child's workflow record, so the sum reproduces
704
845
  // exactly without a second channel. A child that settled by throwing carries
705
846
  // no result and therefore contributes nothing — same rule as its duration.
706
847
  const runUsage = {};
707
- // Child workflow ids the parent itself cancelled (suspension / neverFits halt cascade). Their `getResult`
848
+ // Child workflow ids the parent itself cancelled (the neverFits halt cascade). Their `getResult`
708
849
  // rejects with a DBOS cancellation error and lands in the settlement error branch — but the step was
709
850
  // CANCELED by us, not failed on its own merits. Keyed here (deterministic: `cancelInFlight` runs the
710
851
  // same way on a replay) so that branch records it as canceled everywhere, matching how a child that
@@ -738,26 +879,18 @@ async function runSchedulerLoop(args) {
738
879
  const stack = [...(dependentsByStepId.get(failedStepId) ?? [])];
739
880
  while (stack.length > 0) {
740
881
  const id = stack.pop();
741
- if (stepRuntime.get(id).status === "pending") {
882
+ if (id === undefined)
883
+ break;
884
+ if (presentGet(stepRuntime, id, "the runtime of the step").status === "pending") {
742
885
  stepRuntime.set(id, { status: "skipped" });
743
886
  stack.push(...(dependentsByStepId.get(id) ?? []));
744
887
  }
745
888
  }
746
889
  };
747
- // Halts scheduling entirely — reserved for a suspension of a child and the
748
- // `neverFits` guard. An ordinary step failure does NOT set it: it dooms
749
- // only that step's transitive dependents.
890
+ // Halts scheduling entirely — reserved for the `neverFits` guard. An ordinary
891
+ // step failure does NOT set it: it dooms only that step's transitive dependents.
750
892
  let halted = false;
751
- let suspension = null;
752
893
  let failureReason = null;
753
- const haltForSuspension = async (child) => {
754
- if (suspension === null) {
755
- suspension = { kind: "suspended", reason: child.reason };
756
- halted = true;
757
- failureReason = child.reason;
758
- }
759
- await cancelInFlight(inFlight, child.reason, canceledByParent);
760
- };
761
894
  const stepIndexById = new Map(input.steps.map((s, i) => [s.id, i]));
762
895
  const emitDagSnapshot = async () => {
763
896
  await emitStreamPart({
@@ -765,7 +898,7 @@ async function runSchedulerLoop(args) {
765
898
  id: `dag-${runId}`,
766
899
  runId,
767
900
  steps: input.steps.map((s) => {
768
- const rt = stepRuntime.get(s.id);
901
+ const rt = presentGet(stepRuntime, s.id, "the runtime of the step");
769
902
  const entry = {
770
903
  id: s.id,
771
904
  // `input.steps` is the SCHEDULER's projection — `{id, depends_on}` and nothing
@@ -798,7 +931,6 @@ async function runSchedulerLoop(args) {
798
931
  // the body once `collectAndComplete` has derived the final status.
799
932
  observeRunGuarded(deps, buildRunObservation({ input, runId, status: "running", stepStates: stepRuntime }));
800
933
  };
801
- const dbosParentId = DBOS.workflowID ?? workflowId;
802
934
  const dispatchReady = async () => {
803
935
  if (halted)
804
936
  return;
@@ -832,13 +964,13 @@ async function runSchedulerLoop(args) {
832
964
  });
833
965
  }
834
966
  halted = true;
835
- failureReason = `step "${scheduled.neverFits[0]}" exceeds the machine resource budget`;
967
+ failureReason = `step "${String(scheduled.neverFits[0])}" exceeds the machine resource budget`;
836
968
  await cancelInFlight(inFlight, "fail_fast", canceledByParent);
837
969
  await emitDagSnapshot();
838
970
  return;
839
971
  }
840
972
  for (const stepId of scheduled.heldForCapacity) {
841
- if (stepRuntime.get(stepId).status !== "queued") {
973
+ if (presentGet(stepRuntime, stepId, "the runtime of the step").status !== "queued") {
842
974
  stepRuntime.set(stepId, { status: "queued" });
843
975
  queuedChanged = true;
844
976
  }
@@ -854,33 +986,35 @@ async function runSchedulerLoop(args) {
854
986
  return;
855
987
  }
856
988
  for (const stepId of admit) {
857
- const idx = stepIndexById.get(stepId);
989
+ const idx = presentGet(stepIndexById, stepId, "the index of the step");
858
990
  const childWorkflowId = `${workflowId}-${idx}`;
859
991
  // Compose the seed HERE — the dependencies of `stepId` are all in
860
992
  // `completed`, so this is the first (and only) moment their results
861
993
  // can be named. Checkpointed so a replay re-dispatches the child
862
994
  // with a byte-identical prompt instead of re-reading the DB/disk.
863
995
  const seedStepName = `compose-step-seed:${stepId}`;
864
- const prompt = await DBOS.runStep(() => {
996
+ const checkpointed = await DBOS.runStep(() => {
865
997
  stableSpan(seedStepName, "compose-step-seed", { [ATTR_INFLEXA_STEP_ID]: stepId });
866
998
  return composeStepSeed({ input, stepId, runId, deps });
867
999
  }, { name: seedStepName });
1000
+ // A run recovered from a checkpoint written when this step returned the
1001
+ // bare seed string replays that string: it carries no binding.
1002
+ const seed = typeof checkpointed === "string" ? { prompt: checkpointed } : checkpointed;
868
1003
  const baseChildInput = buildChildInput({
869
1004
  input,
870
1005
  stepId,
871
1006
  level: levels.get(stepId) ?? 0,
872
1007
  runId,
873
- workflowId,
874
- prompt,
1008
+ prompt: seed.prompt,
1009
+ templateBinding: seed.templateBinding,
875
1010
  });
876
1011
  const childInput = {
877
1012
  ...baseChildInput,
878
- parentWorkflowId: dbosParentId,
879
1013
  runSession: forStep(input.runSession, stepId),
880
1014
  };
881
- const handle = (await DBOS.startWorkflow(deps.sandboxStepCallable, {
1015
+ const handle = await DBOS.startWorkflow(deps.sandboxStepCallable, {
882
1016
  workflowID: childWorkflowId,
883
- })(childInput));
1017
+ })(childInput);
884
1018
  inFlight.set(childWorkflowId, { stepId, handle });
885
1019
  stepRuntime.set(stepId, { status: "running" });
886
1020
  }
@@ -894,7 +1028,7 @@ async function runSchedulerLoop(args) {
894
1028
  // function-ID-consuming ops and diverge the counter on replay.
895
1029
  const winner = await DBOS.waitFirst([...inFlight.values()].map((e) => e.handle));
896
1030
  const childId = winner.workflowID;
897
- const entry = inFlight.get(childId);
1031
+ const entry = presentGet(inFlight, childId, "the in-flight entry of the winning child");
898
1032
  inFlight.delete(childId);
899
1033
  let settled;
900
1034
  try {
@@ -904,8 +1038,6 @@ async function runSchedulerLoop(args) {
904
1038
  catch (err) {
905
1039
  settled = { childId, stepId: entry.stepId, kind: "error", err };
906
1040
  }
907
- await drainChildSuspensions(childSuspensions);
908
- const childSuspension = childSuspensions.get(settled.childId);
909
1041
  // Terminal step status + duration for the provenance emission below. Every
910
1042
  // settlement branch assigns both, so the emission fires exactly once per
911
1043
  // settled child with the mapped status.
@@ -918,24 +1050,22 @@ async function runSchedulerLoop(args) {
918
1050
  completed.add(settled.stepId);
919
1051
  stepRuntime.set(settled.stepId, {
920
1052
  status: "completed",
921
- durationMs: r.durationMs ?? undefined,
1053
+ durationMs: r.durationMs,
922
1054
  });
923
1055
  stepStatus = "completed";
924
- stepDurationMs = r.durationMs ?? undefined;
1056
+ stepDurationMs = r.durationMs;
925
1057
  await emitDagSnapshot();
926
1058
  await dispatchReady();
927
1059
  }
928
1060
  else if (r.status === "canceled") {
929
1061
  canceled.add(settled.stepId);
930
- if (childSuspension !== undefined)
931
- await haltForSuspension(childSuspension);
932
1062
  stepRuntime.set(settled.stepId, {
933
1063
  status: "failed",
934
- durationMs: r.durationMs ?? undefined,
1064
+ durationMs: r.durationMs,
935
1065
  error: r.error ?? "canceled",
936
1066
  });
937
1067
  stepStatus = "canceled";
938
- stepDurationMs = r.durationMs ?? undefined;
1068
+ stepDurationMs = r.durationMs;
939
1069
  await emitDagSnapshot();
940
1070
  }
941
1071
  else {
@@ -948,18 +1078,18 @@ async function runSchedulerLoop(args) {
948
1078
  failureReason ??= r.error ?? (r.status === "blocked" ? "step_blocked" : "step_failed");
949
1079
  stepRuntime.set(settled.stepId, {
950
1080
  status: "failed",
951
- durationMs: r.durationMs ?? undefined,
1081
+ durationMs: r.durationMs,
952
1082
  error: r.error ?? (r.status === "blocked" ? "step_blocked" : "step_failed"),
953
1083
  });
954
1084
  markDependentsSkipped(settled.stepId);
955
1085
  stepStatus = "failed";
956
- stepDurationMs = r.durationMs ?? undefined;
1086
+ stepDurationMs = r.durationMs;
957
1087
  await emitDagSnapshot();
958
1088
  await dispatchReady();
959
1089
  }
960
1090
  }
961
1091
  else if (canceledByParent.has(settled.childId)) {
962
- // The child threw because the PARENT cancelled it (suspension / neverFits halt cascade), not
1092
+ // The child threw because the PARENT cancelled it (the neverFits halt cascade), not
963
1093
  // because it failed on its own — its `getResult` rejects with a DBOS cancellation error.
964
1094
  // Record it as canceled, mirroring the graceful `{status:"canceled"}` branch above so the
965
1095
  // `canceled` set, the terminal result, and the `step_completed` provenance all agree. No
@@ -969,34 +1099,22 @@ async function runSchedulerLoop(args) {
969
1099
  // `stepRuntime` (the DAG snapshot) has no canceled tier; the graceful-cancel branch above
970
1100
  // likewise renders canceled steps as "failed", so this stays consistent with it.
971
1101
  status: "failed",
972
- error: settled.err instanceof Error ? settled.err.message : String(settled.err),
1102
+ error: errorMessageOf(settled.err) ?? textOf(settled.err),
973
1103
  });
974
1104
  stepStatus = "canceled";
975
1105
  stepDurationMs = undefined;
976
1106
  await emitDagSnapshot();
977
1107
  }
978
- else if (childSuspension !== undefined) {
979
- // The child suspended: it sent its typed suspension, then cancelled
980
- // itself, thus `getResult` rejects with a DBOS cancellation. The
981
- // suspension halts the run — the cascade is a decision of the host,
982
- // not a DAG one — and it dooms no dependent: the resumed run needs them.
983
- canceled.add(settled.stepId);
984
- await haltForSuspension(childSuspension);
985
- stepRuntime.set(settled.stepId, { status: "failed", error: childSuspension.reason });
986
- stepStatus = "canceled";
987
- stepDurationMs = undefined;
988
- await emitDagSnapshot();
989
- }
990
1108
  else {
991
- // The child threw on its own (not a parent-driven cancel, not a
992
- // suspension). A throw is treated exactly like a step failure:
1109
+ // The child threw on its own (not a parent-driven cancel). A throw is
1110
+ // treated exactly like a step failure:
993
1111
  // dependents doomed, siblings continue.
994
1112
  failed.add(settled.stepId);
995
- failureReason ??= settled.err instanceof Error ? settled.err.message : String(settled.err);
1113
+ failureReason ??= errorMessageOf(settled.err) ?? textOf(settled.err);
996
1114
  markDependentsSkipped(settled.stepId);
997
1115
  stepRuntime.set(settled.stepId, {
998
1116
  status: "failed",
999
- error: settled.err instanceof Error ? settled.err.message : String(settled.err),
1117
+ error: errorMessageOf(settled.err) ?? textOf(settled.err),
1000
1118
  });
1001
1119
  stepStatus = "failed";
1002
1120
  // A child that settled by throwing carries no durable duration.
@@ -1017,30 +1135,19 @@ async function runSchedulerLoop(args) {
1017
1135
  runId,
1018
1136
  stepId: settled.stepId,
1019
1137
  status: stepStatus,
1020
- ...(stepDurationMs !== undefined ? { durationMs: stepDurationMs } : {}),
1138
+ durationMs: stepDurationMs,
1021
1139
  atMs: settledAtMs,
1022
1140
  }, input.runSession);
1023
1141
  }
1024
- await drainChildSuspensions(childSuspensions);
1025
1142
  return {
1026
1143
  completed,
1027
1144
  failed,
1028
1145
  canceled,
1029
- suspension,
1030
1146
  failureReason,
1031
1147
  stepStates: stepRuntime,
1032
- ...(hasReportedUsage(runUsage) ? { usage: { ...runUsage } } : {}),
1148
+ usage: hasReportedUsage(runUsage) ? { ...runUsage } : undefined,
1033
1149
  };
1034
1150
  }
1035
- async function drainChildSuspensions(suspensions) {
1036
- while (true) {
1037
- const msg = await DBOS.recv(CHILD_SUSPENDED_TOPIC, 0);
1038
- if (!msg)
1039
- return;
1040
- if (msg.kind === "suspended" && !suspensions.has(msg.childWorkflowId))
1041
- suspensions.set(msg.childWorkflowId, msg);
1042
- }
1043
- }
1044
1151
  async function cancelInFlight(inFlight, cause, canceledByParent) {
1045
1152
  const ids = [...inFlight.keys()];
1046
1153
  // Record every id BEFORE the cancel so the settlement loop classifies the resulting
@@ -1052,10 +1159,11 @@ async function cancelInFlight(inFlight, cause, canceledByParent) {
1052
1159
  recordCancelledChild({ cause });
1053
1160
  }
1054
1161
  }
1162
+ // ── (6) collectAndComplete ────────────────────────────────────────────
1163
+ /** The waits between the attempts are 1, 2, 4, and 8 seconds. */
1164
+ const FINAL_STATUS_WRITE_RETRY = { retriesAllowed: true, maxAttempts: 5, intervalSeconds: 1, backoffRate: 2 };
1055
1165
  async function collectAndComplete(args) {
1056
- const { input, runId, workflowId, startedAtMs, completed, failed, canceled, suspension, failureReason, forceFailed, deps } = args;
1057
- // A forced failure is terminal and never a resumable suspension.
1058
- const suspended = suspension !== null && !forceFailed ? suspension : null;
1166
+ const { input, runId, workflowId, startedAtMs, completed, failed, canceled, failureReason, forceFailed, deps } = args;
1059
1167
  const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis").with({ runId, analysisId: input.analysisId });
1060
1168
  const status = forceFailed
1061
1169
  ? "failed"
@@ -1064,34 +1172,12 @@ async function collectAndComplete(args) {
1064
1172
  completed,
1065
1173
  failed,
1066
1174
  canceled,
1067
- suspended: suspended !== null,
1068
1175
  });
1069
- const chargeOutcome = status === "completed" || status === "partial"
1070
- ? { kind: "ok" }
1071
- : status === "failed"
1072
- ? { kind: "error" }
1073
- : suspended !== null
1074
- ? { kind: "suspended", reason: suspended.reason }
1075
- : { kind: "canceled" };
1076
- const revokeReason = status === "completed" || status === "partial"
1077
- ? "workflow-completed"
1078
- : status === "failed"
1079
- ? "workflow-failed"
1080
- : suspended !== null
1081
- ? "workflow-suspended"
1082
- : "workflow-canceled";
1083
- // Terminal clock read + run-completion provenance, emitted BEFORE the `cortex_runs`
1084
- // status write below — and thus before any of the terminal cleanup steps. The CLI
1085
- // watches the run by POLLING that row (`run.ts` `waitForRunTerminal`) and shuts down
1086
- // the instant it leaves `running`, flushing provenance and then `process.exit()`ing;
1087
- // emitting the record only after the status write races that shutdown and can drop
1088
- // the run's terminal provenance entirely. Emitting first makes the record dirty
1089
- // (synchronously, via the bus) before the row can be observed terminal, so the CLI's
1090
- // flush always captures it. Not step-wrapped — a DBOS recovery replay must re-fire it;
1091
- // `DBOS.now()` is checkpointed, so the span stays replay-stable (see `RunProvenanceEvent`).
1092
- // The duration measures from the `run_started` read threaded in as `startedAtMs`.
1176
+ const chargeOutcome = status === "completed" || status === "partial" ? { kind: "ok" } : status === "failed" ? { kind: "error" } : { kind: "canceled" };
1177
+ const revokeReason = status === "completed" || status === "partial" ? "workflow-completed" : status === "failed" ? "workflow-failed" : "workflow-canceled";
1178
+ const finalError = failureReason ?? (status === "canceled" ? "external_cancel" : null);
1093
1179
  const terminalAtMs = await DBOS.now();
1094
- emitProvenanceGuarded(deps, {
1180
+ const emitRunEnd = () => emitProvenanceGuarded(deps, {
1095
1181
  type: "run_completed",
1096
1182
  analysisId: input.analysisId,
1097
1183
  runId,
@@ -1099,12 +1185,53 @@ async function collectAndComplete(args) {
1099
1185
  atMs: terminalAtMs,
1100
1186
  durationMs: terminalAtMs - startedAtMs,
1101
1187
  }, input.runSession);
1102
- await ledgerStep(logger.with({ status }), "persist-final-status", async () => {
1103
- const written = await updateRunStatus(deps.pool, runId, status, failureReason ?? (status === "canceled" ? "external_cancel" : null));
1104
- if (written.isErr())
1105
- return written;
1106
- // The outcome metrics ride inside the step that persists the terminal status: DBOS
1107
- // caches the step, so a recovered body records nothing again. A step's own terminal
1188
+ const closeCharge = () => deliverNotice(logger.with({ chargeOutcome: chargeOutcome.kind }), "RunCharge.close", deps.runCharge.close({
1189
+ analysisId: input.analysisId,
1190
+ runId,
1191
+ outcome: chargeOutcome,
1192
+ session: input.runSession,
1193
+ }));
1194
+ const revokeAuthorization = () => deliverNotice(logger.with({ revokeReason }), "RunAuthorizer.revoke", deps.runAuthorizer.revoke(input.runSession, revokeReason));
1195
+ // The writer that wins the row owns the run end: the `run_completed` emit, the charge close,
1196
+ // and the revoke. The step does all three when its write changes the row. An engine cancel
1197
+ // cannot stop a step that started, so a won write is never cut before these acts.
1198
+ //
1199
+ // The emit rides inside the transaction of the write. The update holds the row lock until the
1200
+ // commit, so the run canceler waits and then loses, and no reader sees the terminal row before
1201
+ // the emit. The cli polls the row and flushes the provenance when it leaves `running`; an emit
1202
+ // after the commit can miss that flush. `emitRunEvent` returns `void`, so the emit completes before the commit.
1203
+ //
1204
+ // DBOS retries only a thrown error, so an attempt before the last throws on a failed transaction.
1205
+ // The last attempt returns "failed" instead: the body takes the failed path with no `try`, and a
1206
+ // failure of DBOS to record a won step cannot be mistaken for a failed write. A commit can fail
1207
+ // after the emit, so `emittedInStep` keeps the later attempts from emitting again.
1208
+ // The `undefined` in the annotation exists only because control-flow analysis cannot see the closure assignment; no code may depend on the initial state.
1209
+ let emittedInStep;
1210
+ const finalWrite = await DBOS.runStep(async () => {
1211
+ const stepLog = logger.with({ status }).named("persist-final-status");
1212
+ const written = await withTransaction(deps.pool, "runs.persistFinalStatus", (client) => updateRunStatus(client, runId, status, finalError).map((changed) => {
1213
+ if (changed && !emittedInStep) {
1214
+ emitRunEnd();
1215
+ emittedInStep = true;
1216
+ }
1217
+ return changed;
1218
+ }));
1219
+ if (written.isErr()) {
1220
+ const attempt = DBOS.stepStatus;
1221
+ const isLastAttempt = attempt?.currentAttempt === undefined || attempt.maxAttempts === undefined || attempt.currentAttempt >= attempt.maxAttempts;
1222
+ const failure = { error: describeDbError(written.error), ...stepLog.errorFields(written.error.cause) };
1223
+ if (isLastAttempt) {
1224
+ stepLog.error("ledger write failed", failure);
1225
+ return "failed";
1226
+ }
1227
+ stepLog.warn("ledger write failed, the step retries", { ...failure, attempt: attempt.currentAttempt });
1228
+ throw new Error(`ledger write failed: ${describeDbError(written.error)}`, { cause: written.error.cause });
1229
+ }
1230
+ // A refused write means an earlier write already set a terminal status: the run
1231
+ // canceler's, or an earlier run of this step before a crash. That writer owns the run end.
1232
+ if (!written.value)
1233
+ return "refused";
1234
+ // DBOS caches the step, so a recovered body records nothing again. A step's own terminal
1108
1235
  // row already carried its metric; the residue counted here is every settled step
1109
1236
  // without one — a child the cancel cut before `mark-canceled` (the cancel check runs
1110
1237
  // before a step body), or one that died before `failStep`. The ledger read is kept
@@ -1121,8 +1248,41 @@ async function collectAndComplete(args) {
1121
1248
  recordStepCompleted({ agentId: row.agentId, status: "failed" });
1122
1249
  }
1123
1250
  recordRunCompleted({ workflow: "analysis", status, durationMs: terminalAtMs - startedAtMs });
1124
- return written;
1125
- });
1251
+ await closeCharge();
1252
+ await revokeAuthorization();
1253
+ return "won";
1254
+ }, { name: "persist-final-status", ...FINAL_STATUS_WRITE_RETRY });
1255
+ // A refused write leaves two owners. When the canceler won, its engine cancel came before its
1256
+ // write, so `close-running-charge` throws the cancel and the body does no act of the run end.
1257
+ // When a recovery runs the status step again after its own write landed, no cancel occurred and
1258
+ // the body completes the run end here. A failed write leaves the row `running` with no owner,
1259
+ // and the body still closes and revokes.
1260
+ let closed = false;
1261
+ let revoked = false;
1262
+ if (finalWrite !== "won") {
1263
+ closed = await DBOS.runStep(closeCharge, { name: "close-running-charge" });
1264
+ revoked = await DBOS.runStep(revokeAuthorization, { name: "revoke-run-auth" });
1265
+ }
1266
+ // The step emits only in the execution that ran it. A won step that DBOS gives from the cache
1267
+ // did not run, and a refused or failed write did not emit; the body fires the run end here
1268
+ // with the same checkpointed values. It is not step-wrapped, so a recovery replay fires it again.
1269
+ if (!emittedInStep)
1270
+ emitRunEnd();
1271
+ // After a failed write the row is not terminal, so the body records its run end for the run
1272
+ // canceler. The record comes after the acts: a cancel before it stops the body and the canceler
1273
+ // then owns the run end. A cancel is the only error that goes back to DBOS; a failed record must
1274
+ // not undo an otherwise-complete run, like the sibling terminal steps.
1275
+ if (finalWrite === "failed") {
1276
+ const recorded = { status, error: finalError, closed, revoked };
1277
+ try {
1278
+ await DBOS.setEvent(RUN_END_EVENT_KEY, recorded);
1279
+ }
1280
+ catch (error) {
1281
+ if (isWorkflowCancellation(error))
1282
+ throw error;
1283
+ logger.error("run end record failed, the run canceler acts as for a run with no record", { status, ...logger.errorFields(error) });
1284
+ }
1285
+ }
1126
1286
  // Record the run's synthesis outcome AFTER the status write — a reader that
1127
1287
  // sees a terminal status then also sees whether (and why) synthesis ran. A
1128
1288
  // null outcome means synthesis never ran; leave the columns NULL ("unknown").
@@ -1132,40 +1292,17 @@ async function collectAndComplete(args) {
1132
1292
  if (outcome !== null) {
1133
1293
  await ledgerStep(logger.with({ synthesisStatus: outcome.status }), "persist-synthesis-outcome", () => setRunSynthesisOutcome(deps.pool, runId, outcome.status, outcome.reason));
1134
1294
  }
1135
- // Sweep never-started steps to `skipped` — but ONLY on genuinely-terminal
1136
- // paths. The gate is the BRANCH, not the written run status: the pending rows
1137
- // of a suspended run must survive for a later resume. Log-don't-rollback, like
1138
- // every other finalisation step here.
1139
- if (suspended === null) {
1140
- await ledgerStep(logger, "sweep-pending-steps", () => sweepPendingStepExecutions(deps.pool, runId));
1141
- }
1142
- if (suspended !== null) {
1143
- await ledgerStep(logger, "suspend-analysis", () => suspendAnalysisQuery(deps.pool, input.analysisId));
1144
- }
1145
- await DBOS.runStep(() => deliverNotice(logger.with({ chargeOutcome: chargeOutcome.kind }), "RunCharge.close", deps.runCharge.close({
1146
- analysisId: input.analysisId,
1147
- runId,
1148
- outcome: chargeOutcome,
1149
- session: input.runSession,
1150
- })), { name: "close-running-charge" });
1151
- // Revoke the run authorization for the terminal run state. Ownership
1152
- // defaults to true for inputs persisted before the field existed (a
1153
- // workflow recovered across this deploy) — those were always Cortex-owned.
1154
- const authorization = {
1155
- runSession: input.runSession,
1156
- ownsMandate: input.ownsMandate ?? true, // oss-core-managed-ok
1157
- };
1158
- await DBOS.runStep(() => deliverNotice(logger.with({ revokeReason }), "RunAuthorizer.revoke", deps.runAuthorizer.revoke(authorization, revokeReason)), {
1159
- name: "revoke-run-auth",
1160
- });
1161
- // The `run_completed` provenance was already emitted above (before the status write, to
1162
- // beat the CLI's poll-and-shutdown). These branches only fan out the UI stream part, whose
1295
+ // Sweep never-started steps to `skipped`. Log-don't-rollback, like every other finalisation step here.
1296
+ await ledgerStep(logger, "sweep-pending-steps", () => sweepPendingStepExecutions(deps.pool, runId));
1297
+ // A step that the neverFits halt canceled in its pause leaves its row, thus the end of the run clears each row of the run.
1298
+ await ledgerStep(logger, "clear-run-pauses", () => clearPausesOfWork(deps.pool, runId));
1299
+ // The run end was emitted above. These branches only fan out the UI stream part, whose
1163
1300
  // completed/failed shapes genuinely differ — the provenance record does not.
1164
1301
  if (status === "completed" || status === "partial") {
1165
1302
  const artifactCount = await DBOS.runStep(async () => await countArtifactsByRuns(deps.pool, input.analysisId, [runId])
1166
1303
  .map((counts) => counts[0]?.count ?? 0)
1167
1304
  .unwrapOr(0), { name: "count-run-artifacts" });
1168
- await emitStreamPart({
1305
+ const runCompleted = {
1169
1306
  type: "data-run-completed",
1170
1307
  runId,
1171
1308
  status,
@@ -1176,20 +1313,18 @@ async function collectAndComplete(args) {
1176
1313
  title: f.title,
1177
1314
  confidence: f.confidence,
1178
1315
  })),
1179
- ...(status === "partial"
1180
- ? {
1181
- note: `${completed.size} of ${input.steps.length} steps completed; results are partial.`,
1182
- }
1183
- : {}),
1184
- ...(args.usage ? { usage: args.usage } : {}),
1185
- });
1316
+ };
1317
+ if (status === "partial")
1318
+ runCompleted.note = `${completed.size} of ${input.steps.length} steps completed; results are partial.`;
1319
+ if (args.usage)
1320
+ runCompleted.usage = args.usage;
1321
+ await emitStreamPart(runCompleted);
1186
1322
  }
1187
1323
  else {
1188
1324
  await emitStreamPart({
1189
1325
  type: "data-run-failed",
1190
1326
  runId,
1191
- error: failureReason ?? (suspended !== null ? suspended.reason : "Run canceled before completion"),
1192
- ...(suspended !== null ? { reason: suspended.reason } : {}),
1327
+ error: failureReason ?? "Run canceled before completion",
1193
1328
  });
1194
1329
  }
1195
1330
  return {
@@ -1208,9 +1343,7 @@ async function collectAndComplete(args) {
1208
1343
  * status vocabulary without a cast.
1209
1344
  */
1210
1345
  function deriveFinalStatus(args) {
1211
- const { totalSteps, completed, failed, canceled, suspended } = args;
1212
- if (suspended)
1213
- return "suspended_insufficient_funds";
1346
+ const { totalSteps, completed, failed, canceled } = args;
1214
1347
  if (failed.size > 0) {
1215
1348
  return completed.size > 0 ? "partial" : "failed";
1216
1349
  }