@jr2/orchestrator 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +442 -0
  10. package/docs/concepts/architecture.md +230 -0
  11. package/docs/concepts/charts.md +143 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +254 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +603 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +289 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +222 -0
  20. package/docs/concepts/the-menu.md +378 -0
  21. package/docs/concepts/workspaces.md +678 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  27. package/docs/get-started/install.md +126 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +223 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/fix-a-failed-converge.md +361 -0
  34. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  35. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  36. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  37. package/docs/guides/look-inside-a-sandbox.md +174 -0
  38. package/docs/guides/mirror-the-kit-images.md +176 -0
  39. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  40. package/docs/guides/open-the-console.md +91 -0
  41. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  42. package/docs/guides/push-and-sign-a-branch.md +486 -0
  43. package/docs/guides/run-a-chart-by-name.md +270 -0
  44. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  45. package/docs/guides/run-one-worker-per-item.md +321 -0
  46. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  47. package/docs/guides/size-a-workspace.md +204 -0
  48. package/docs/guides/start-a-run-from-a-run.md +237 -0
  49. package/docs/guides/start-a-run.md +207 -0
  50. package/docs/guides/upgrade-the-kit-version.md +240 -0
  51. package/docs/guides/use-a-hosted-provider.md +208 -0
  52. package/docs/guides/use-a-local-model.md +313 -0
  53. package/docs/guides/watch-a-run.md +326 -0
  54. package/docs/guides/write-a-workflow.md +318 -0
  55. package/docs/index.md +44 -0
  56. package/docs/llms.txt +216 -0
  57. package/docs/reference/authoring-surface/agent.md +128 -0
  58. package/docs/reference/authoring-surface/answer.md +140 -0
  59. package/docs/reference/authoring-surface/arg.md +60 -0
  60. package/docs/reference/authoring-surface/basic.md +62 -0
  61. package/docs/reference/authoring-surface/bearer.md +53 -0
  62. package/docs/reference/authoring-surface/chartschema.md +23 -0
  63. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  64. package/docs/reference/authoring-surface/command.md +191 -0
  65. package/docs/reference/authoring-surface/customize.md +64 -0
  66. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  67. package/docs/reference/authoring-surface/defineevent.md +114 -0
  68. package/docs/reference/authoring-surface/dispatch.md +126 -0
  69. package/docs/reference/authoring-surface/fail.md +83 -0
  70. package/docs/reference/authoring-surface/file.md +122 -0
  71. package/docs/reference/authoring-surface/frompick.md +277 -0
  72. package/docs/reference/authoring-surface/http.md +159 -0
  73. package/docs/reference/authoring-surface/isfailure.md +58 -0
  74. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  75. package/docs/reference/authoring-surface/open.md +74 -0
  76. package/docs/reference/authoring-surface/openbody.md +86 -0
  77. package/docs/reference/authoring-surface/paramsof.md +125 -0
  78. package/docs/reference/authoring-surface/pool.md +186 -0
  79. package/docs/reference/authoring-surface/push.md +112 -0
  80. package/docs/reference/authoring-surface/secret.md +118 -0
  81. package/docs/reference/authoring-surface/sentby.md +83 -0
  82. package/docs/reference/authoring-surface/source.md +51 -0
  83. package/docs/reference/authoring-surface/text.md +62 -0
  84. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  85. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  86. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  87. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  88. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  89. package/docs/reference/authoring-surface/type-arg.md +19 -0
  90. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  91. package/docs/reference/authoring-surface/type-binding.md +62 -0
  92. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  93. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  94. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  95. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  96. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  97. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  98. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  99. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  100. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  101. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  102. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  103. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  104. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  105. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  106. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  107. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  108. package/docs/reference/authoring-surface/type-childat.md +12 -0
  109. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  110. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  111. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  112. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  113. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  114. package/docs/reference/authoring-surface/type-customize.md +109 -0
  115. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  116. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  117. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  118. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  119. package/docs/reference/authoring-surface/type-duration.md +15 -0
  120. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  121. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  122. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  124. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  125. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  126. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  127. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  128. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  129. package/docs/reference/authoring-surface/type-failure.md +19 -0
  130. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  131. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  132. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  133. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  134. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  135. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  136. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  137. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  138. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  139. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  140. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  141. package/docs/reference/authoring-surface/type-lends.md +38 -0
  142. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  143. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  144. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  145. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  146. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  147. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  148. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  149. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  150. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  151. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  152. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  153. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  154. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  155. package/docs/reference/authoring-surface/type-openof.md +12 -0
  156. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  157. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  158. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  159. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  160. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  161. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  162. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  163. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  164. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  165. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  166. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  167. package/docs/reference/authoring-surface/type-reached.md +12 -0
  168. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  169. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  170. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  171. package/docs/reference/authoring-surface/type-secret.md +25 -0
  172. package/docs/reference/authoring-surface/type-sender.md +19 -0
  173. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  174. package/docs/reference/authoring-surface/type-size.md +85 -0
  175. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  176. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  177. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  178. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  179. package/docs/reference/authoring-surface/type-text.md +19 -0
  180. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  181. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  182. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  183. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  184. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  185. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  186. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  187. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  188. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  189. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  190. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  191. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  192. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  193. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  194. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  195. package/docs/reference/authoring-surface/workspace.md +259 -0
  196. package/docs/reference/authoring-surface.md +197 -0
  197. package/docs/reference/cli.md +433 -0
  198. package/docs/reference/config.md +756 -0
  199. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  200. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  201. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  202. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  203. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  204. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  205. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  206. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  207. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  208. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  209. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  210. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  211. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  212. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  213. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  214. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  215. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  216. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  217. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  218. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  219. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  220. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  221. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  222. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  223. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  224. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  225. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  226. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  227. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  228. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  229. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  230. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  231. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  232. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  233. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  234. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  235. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  236. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  237. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  238. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  239. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  240. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  241. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  242. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  243. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  244. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  245. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  246. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  247. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  248. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  249. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  250. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  251. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  252. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  253. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  254. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  255. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  256. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  257. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  258. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  259. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  260. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  261. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  262. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  263. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  264. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  265. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  266. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  267. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  268. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  269. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  270. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  271. package/docs/reference/decisions.md +83 -0
  272. package/docs/reference/glossary.md +648 -0
  273. package/docs/reference/http-api.md +1348 -0
  274. package/docs/reference/machines.md +190 -0
  275. package/docs/reference/mechanism-events.md +72 -0
  276. package/docs/why.md +206 -0
  277. package/package.json +5 -3
  278. package/src/actor.ts +541 -79
  279. package/src/agent.ts +114 -38
  280. package/src/ambient.ts +2 -1
  281. package/src/assembly-checks.ts +189 -7
  282. package/src/cause.ts +234 -0
  283. package/src/chart.ts +169 -37
  284. package/src/charts.ts +34 -6
  285. package/src/config.ts +430 -178
  286. package/src/custodian.ts +74 -22
  287. package/src/customize.ts +188 -46
  288. package/src/dispatch-effect.ts +945 -0
  289. package/src/door-compat.ts +217 -0
  290. package/src/durability.ts +11 -4
  291. package/src/effect.ts +341 -44
  292. package/src/effector-wire.ts +160 -11
  293. package/src/fail.ts +209 -0
  294. package/src/harness-client.ts +541 -47
  295. package/src/http-effect.ts +495 -53
  296. package/src/http.ts +427 -16
  297. package/src/index.ts +60 -82
  298. package/src/instance.ts +68 -8
  299. package/src/internal.ts +84 -0
  300. package/src/machine-doc.ts +21 -3
  301. package/src/mechanism-routes.ts +168 -0
  302. package/src/model-keys.generated.ts +53 -0
  303. package/src/model-keys.ts +18 -58
  304. package/src/notices.ts +66 -13
  305. package/src/open-body.ts +159 -23
  306. package/src/open-effect.ts +81 -8
  307. package/src/open.ts +59 -6
  308. package/src/parts.ts +184 -13
  309. package/src/party.ts +68 -2
  310. package/src/pick.ts +463 -11
  311. package/src/pool.ts +247 -36
  312. package/src/registration.ts +83 -4
  313. package/src/repos.ts +2 -1
  314. package/src/run-host.ts +565 -31
  315. package/src/run-output.ts +38 -0
  316. package/src/sandbox-effect.ts +273 -38
  317. package/src/sandbox-kube.ts +3 -1
  318. package/src/secret-placement.ts +52 -17
  319. package/src/server.ts +3 -0
  320. package/src/setup.ts +239 -56
  321. package/src/size.ts +121 -9
  322. package/src/snapshot-store.ts +54 -10
  323. package/src/stub-harness.ts +44 -16
  324. package/src/timers.ts +14 -1
  325. package/src/vocabulary.ts +69 -14
  326. package/src/wire.ts +90 -16
  327. package/src/workspace.ts +697 -175
@@ -21,11 +21,14 @@ import { z } from "zod";
21
21
 
22
22
  export const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
23
23
  export const HttpMethodShape = z.enum(HTTP_METHODS);
24
+ /** The method of an `http` Effect: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`. */
24
25
  export type HttpMethod = z.infer<typeof HttpMethodShape>;
25
26
 
26
27
  /** One part of a string the Effector builds: literal text, or a secret it holds, by name. A
27
28
  * secret's value is never on the wire. */
28
29
  export const TextPartShape = z.union([z.strictObject({ text: z.string() }), z.strictObject({ secret: z.string() })]);
30
+ /** One part of a {@link Text}: `{ text }`, literal text, or `{ secret }`, the name of an Effect
31
+ * secret. The `text` template makes the parts. */
29
32
  export type TextPart = z.infer<typeof TextPartShape>;
30
33
 
31
34
  export const WireAuthShape = z.union([
@@ -71,7 +74,8 @@ const HEADER_NAME = /^[a-z0-9!#$%&'*+.^_`|~-]+$/;
71
74
  export const isEffectHeader = (name: string): boolean =>
72
75
  HEADER_NAME.test(name) && !FRAMING_HEADERS.has(name) && !name.startsWith("proxy-");
73
76
 
74
- /** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector serves. */
77
+ /** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector
78
+ * serves. */
75
79
  export const HttpStartShape = z.strictObject({
76
80
  kind: z.literal("http"),
77
81
  method: HttpMethodShape,
@@ -103,7 +107,8 @@ export type Pair = z.infer<typeof PairShape>;
103
107
  /** A `push` start: the Workspace's branch to the Repo Slot's Binding, with the push credential of
104
108
  * the `git.credentials` entry the url falls under — the Effector's config holds the entries, and
105
109
  * matches the url as the fence does (`credential` names one by its `match` instead). `pin` is the
106
- * commit; absent, the Effector reads the Agent's branch tip and its answer carries it (`pinned`). */
110
+ * commit; absent, the Effector reads the Agent's branch tip and its answer carries it (`pinned`).
111
+ */
107
112
  export const PushStartShape = z.strictObject({
108
113
  kind: z.literal("push"),
109
114
  push: z.strictObject({
@@ -142,8 +147,71 @@ export const CommandStartShape = z.strictObject({
142
147
  });
143
148
  export type CommandStartRequest = z.infer<typeof CommandStartShape>;
144
149
 
145
- /** Every start: the http kinds, a push, a credentialed command. */
146
- export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape]);
150
+ /** A string the Effector builds, as parts (the `text` template's wire form). */
151
+ export const TextShape = z.array(TextPartShape);
152
+
153
+ /** A secret the Effector holds, by name. */
154
+ export const SecretRefShape = z.strictObject({ secret: z.string() });
155
+
156
+ /** A Workflow's name as `jr2 run` names it (ADR-0071): a Machine's filename stem, or
157
+ * `<wrapper>/<name>` for a named Chart. The Go side's `Validate` holds the same rule. */
158
+ export const WORKFLOW_NAME = /^[a-z0-9][a-z0-9-]*(\/[a-z0-9][a-z0-9-]*)?$/;
159
+
160
+ /** A dispatch target's origin: a scheme and a host — a DNS name or IPv4 address, or a bracketed
161
+ * IPv6 address — a port when it is not the scheme's, and no path, query, fragment, userinfo or any
162
+ * other character no origin has — the Effector appends the door's path (ADR-0071). The Go side's
163
+ * `origin` is the same rule. */
164
+ export const ORIGIN = /^https?:\/\/(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9.-]+)(:[0-9]{1,5})?$/;
165
+
166
+ /**
167
+ * Which run started a run, and its place in the chain (ADR-0071): the caller's run and slot, the
168
+ * caller's ledger row id (`effect`, the door's idempotency key), the dispatched run's depth — one
169
+ * below its caller, stated by the caller and recorded as sent, so at least 1 — and, for a caller
170
+ * on another Orchestrator, that Instance as opaque text.
171
+ */
172
+ export const DispatchedByShape = z.strictObject({
173
+ run: z.string(),
174
+ slot: z.string(),
175
+ effect: z.string(),
176
+ depth: z.number().int().positive(),
177
+ instance: z.string().optional(),
178
+ });
179
+
180
+ /**
181
+ * A `dispatch` start (ADR-0071), which only the Instance Effector serves: a run of `workflow` on the
182
+ * Orchestrator at `url` (its origin, one text part), the body `input` whole, the Instance token as a
183
+ * bearer by name, `dispatchedBy` as the `jr2-dispatched-by` header — `instance` set, since the
184
+ * caller is foreign by construction — and, with `wait`, a poll of `GET /runs/<id>` until the run
185
+ * ends or `timeoutMs` passes.
186
+ */
187
+ export const DispatchStartShape = z.strictObject({
188
+ kind: z.literal("dispatch"),
189
+ url: TextShape.refine(
190
+ (parts) => parts.length === 1 && "text" in parts[0]! && ORIGIN.test(parts[0].text),
191
+ "a dispatch url is one text part, an origin: http(s)://host[:port]",
192
+ ),
193
+ workflow: z.string().regex(WORKFLOW_NAME),
194
+ input: z.record(z.string(), z.unknown()),
195
+ auth: z.strictObject({ bearer: SecretRefShape }),
196
+ // Every id non-empty, as the door's header shape requires (http.ts): an empty one would be the
197
+ // door's 400, which reads as a defect of the input. The effect is a ledger row id, the key. The Go
198
+ // side's `validateDispatch` holds the same rule.
199
+ dispatchedBy: DispatchedByShape.extend({
200
+ run: z.string().min(1),
201
+ slot: z.string().min(1),
202
+ effect: z
203
+ .string()
204
+ .regex(/^[A-Za-z0-9._-]{1,128}$/)
205
+ .refine((id) => id !== "." && id !== ".."),
206
+ instance: z.string().min(1),
207
+ }),
208
+ wait: z.boolean(),
209
+ timeoutMs: z.number().int().positive(),
210
+ });
211
+ export type DispatchStartRequest = z.infer<typeof DispatchStartShape>;
212
+
213
+ /** Every start: the http kinds, a push, a credentialed command, a dispatch. */
214
+ export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape, DispatchStartShape]);
147
215
  export type StartRequest = z.infer<typeof StartRequestShape>;
148
216
 
149
217
  /** The most bytes of a start's body the Effector reads (its `maxRequestBody`). A longer body
@@ -175,17 +243,68 @@ export const HttpOutputShape = z.union([
175
243
  HttpResponseShape,
176
244
  z.strictObject({ outcome: z.enum(["unreachable", "refused", "unknown"]), reason: z.string() }),
177
245
  ]);
246
+ /**
247
+ * The output of an `http` Effect: what the `onDone` of its state gets as `event.output`. Every
248
+ * outcome is output. A 4xx or 5xx status is output too. Route on `outcome`:
249
+ *
250
+ * - `response`: an answer came back. `status` is its HTTP status. `headers`, `contentType` and
251
+ * `size` tell about the body. `body` is there when the body is text: at most `maxBody` bytes
252
+ * (`truncated` is `true` when jr2 cut it). A body that is not text gives only its size and
253
+ * content type. jr2 replaces each value of an Effect secret that the Effector holds, and its
254
+ * usual encodings, with `[secret:<name>]`, in the body and the headers.
255
+ * - `unreachable`: the call did not go out, for example because the name did not resolve, or the
256
+ * connection or TLS failed. `reason` tells why.
257
+ * - `refused`: jr2 or the Effector refused the call, and nothing was sent. For example, a path
258
+ * segment that is `.`, `..` or empty, or a request that is too large. `reason` tells why.
259
+ * - `unknown`: the call can have gone out, but no answer came, or jr2 cannot tell after a restart.
260
+ * `reason` tells why. jr2 does not send it again unless it is marked `idempotent`.
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * import { fail, http, jr2Setup, type HttpOutput } from "@jr2/orchestrator";
265
+ *
266
+ * const ok = (output: HttpOutput): boolean => output.outcome === "response" && output.status < 300;
267
+ *
268
+ * export const machine = jr2Setup({
269
+ * actors: { health: http({ method: "GET", url: "https://app.example.com/healthz" }), fail: fail() },
270
+ * }).createMachine({
271
+ * id: "health",
272
+ * initial: "checking",
273
+ * states: {
274
+ * checking: {
275
+ * invoke: {
276
+ * src: "health",
277
+ * onDone: [
278
+ * { guard: ({ event }) => ok(event.output), target: "healthy" },
279
+ * { target: "unhealthy" },
280
+ * ],
281
+ * },
282
+ * },
283
+ * healthy: { type: "final" },
284
+ * // Not a final state: the run ends `failed`, and `jr2 run` exits 1.
285
+ * unhealthy: { invoke: { src: "fail", input: { reason: "the app is not healthy" } } },
286
+ * },
287
+ * });
288
+ * ```
289
+ *
290
+ * @see docs/concepts/effects.md
291
+ * @see docs/guides/run-a-command-or-call-a-url.md
292
+ * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
293
+ */
178
294
  export type HttpOutput = z.infer<typeof HttpOutputShape>;
179
295
 
180
296
  /**
181
297
  * A `push` Effect's output (ADR-0067). `pushed`: the published commit, its source (the Agent's
182
298
  * commit), what the remote held before, how the new commits were found after a signed push
183
- * (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `squashed`
184
- * when the published commit is one squashed commit (`signed | unsigned`; absent for signed
185
- * copies; what the published commit is, an earlier push's included, never only this push's
186
- * options), and — when the published commit is not the Agent's own — the published commits as a
187
- * bundle for the sync. `rejected`: a stage and git's words. `diverged`: the
188
- * published commit is not in the Agent's history and nothing paired it; nothing was pushed.
299
+ * (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `newCommits`
300
+ * (how many commits the published commit adds to what jr2 found held, the remote's branch and the
301
+ * Repo cache; 0 when jr2 found the published commit already held, which the Agent reads in words,
302
+ * menu.ts), `squashed` when the published
303
+ * commit is one squashed commit (`signed | unsigned`; absent for signed copies; what the published
304
+ * commit is, an earlier push's included, never only this push's options), and — when the published
305
+ * commit is not the Agent's own — the published commits as a bundle for the sync. `rejected`: a
306
+ * stage and git's words. `diverged`: the published commit is not in the Agent's history and
307
+ * nothing paired it; nothing was pushed.
189
308
  */
190
309
  export const PushOutputShape = z.union([
191
310
  z.strictObject({
@@ -195,6 +314,9 @@ export const PushOutputShape = z.union([
195
314
  previous: z.string().optional(),
196
315
  via: z.string().optional(),
197
316
  unchanged: z.boolean().optional(),
317
+ // Optional: a live Sandbox keeps the runtime it started with (ADR-0037), so its older Effector
318
+ // sends no count after `jr2 up`. With no count the Harness makes no claim (menu.ts).
319
+ newCommits: z.number().int().nonnegative().optional(),
198
320
  squashed: z.enum(["signed", "unsigned"]).optional(),
199
321
  bundle: z.string().optional(),
200
322
  }),
@@ -207,6 +329,7 @@ export const PushOutputShape = z.union([
207
329
  z.strictObject({ outcome: z.literal("diverged"), reason: z.string(), published: z.string(), source: z.string() }),
208
330
  z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
209
331
  ]);
332
+ /** @internal */
210
333
  export type PushWireOutput = z.infer<typeof PushOutputShape>;
211
334
 
212
335
  /** A credentialed command's output, as the command kind's (ADR-0067), and `dirty` when the
@@ -233,6 +356,29 @@ export const CommandOutputShape = z.union([
233
356
  ]);
234
357
  export type CommandWireOutput = z.infer<typeof CommandOutputShape>;
235
358
 
359
+ /**
360
+ * A `dispatch` Effect's output on the wire (ADR-0071): its own outcome set. `settled.status` is the
361
+ * callee's status as the door served it, in the store's words, and `output` the callee Machine's,
362
+ * passed through scrubbed; `refused` is the door's status and its report, told from an http
363
+ * `refused` (which has a `reason`) by its fields.
364
+ */
365
+ export const DispatchOutputShape = z.discriminatedUnion("outcome", [
366
+ z.strictObject({ outcome: z.literal("started"), run: z.string() }),
367
+ z.strictObject({
368
+ outcome: z.literal("settled"),
369
+ run: z.string(),
370
+ // The store's words (ADR-0025) and the timeout's `live`, as `DispatchOutput` types them for the
371
+ // Machine: a door that serves another word (another Kit version) is a malformed answer, the
372
+ // invoke's error, never a status the Machine's type does not name (ADR-0071).
373
+ status: z.enum(["done", "failed", "error", "cancelled", "lost", "drifted", "live"]),
374
+ output: z.unknown().optional(),
375
+ }),
376
+ z.strictObject({ outcome: z.literal("refused"), status: z.number().int(), report: z.array(z.string()) }),
377
+ z.strictObject({ outcome: z.literal("unreachable"), reason: z.string() }),
378
+ z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
379
+ ]);
380
+ export type DispatchWireOutput = z.infer<typeof DispatchOutputShape>;
381
+
236
382
  /** The replica that holds an Effect, and where to poll it. */
237
383
  export const ReplicaShape = z.strictObject({ name: z.string(), address: z.string() });
238
384
  export type Replica = z.infer<typeof ReplicaShape>;
@@ -245,7 +391,10 @@ export const EffectStateShape = z.strictObject({
245
391
  /** The commit a push or a credentialed command runs against, read when it started: the
246
392
  * Orchestrator writes it to the ledger's intent row. */
247
393
  pinned: z.string().optional(),
248
- output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape]).optional(),
394
+ /** The run a dispatch started, once the door answered and while its wait runs: the Orchestrator
395
+ * writes it to the ledger's intent row, so the caller names its callee during the wait (ADR-0071). */
396
+ run: z.string().optional(),
397
+ output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape]).optional(),
249
398
  });
250
399
  export type EffectState = z.infer<typeof EffectStateShape>;
251
400
 
package/src/fail.ts ADDED
@@ -0,0 +1,209 @@
1
+ // The `fail()` Part (ADR-0073): the end that a Machine chooses for work that did not succeed — a
2
+ // Failure (GLOSSARY.md). It is a Part and not an Effect, because it does no work outside the
3
+ // Machine: its actor throws an error that carries a brand, and xstate sends that error up through
4
+ // each parent Machine by its own rules. An `onError` on the way catches it; `isFailure` tells it
5
+ // from a defect. When no state catches it, the run host ends the run `failed`.
6
+ //
7
+ // The brand is a registry symbol (`Symbol.for`), not a module symbol, for the reason the walk is
8
+ // structural (parts.ts): an Instance resolves its OWN `@jr2/orchestrator`, so the copy that throws
9
+ // and the copy that reads the error (the run host, the CLI's walk) can be two module instances. The
10
+ // brand is not exported: an author makes a Failure only with `fail()`.
11
+
12
+ import { fromPromise, type PromiseActorLogic } from "xstate";
13
+
14
+ const FAILURE: unique symbol = Symbol.for("jr2.failure") as never;
15
+ const FAIL_SLOT: unique symbol = Symbol.for("jr2.fail.slot") as never;
16
+
17
+ /** What a Failure carries: `reason`, text for a person, and `detail`, optional JSON for a machine
18
+ * reader. The input of `fail()` and the output of a run that ends `failed`. */
19
+ export type FailureInput = { reason: string; detail?: unknown };
20
+
21
+ /** A Failure as `isFailure` narrows an error to it: the payload's fields, on the error itself. */
22
+ export type Failure = { readonly reason: string; readonly detail?: unknown };
23
+
24
+ /** Make the branded error. Only this module makes one. */
25
+ function failure(input: FailureInput): Error & Failure {
26
+ const error = new Error(input.reason) as Error & Failure & { [FAILURE]: true };
27
+ error.name = "Failure";
28
+ Object.defineProperty(error, "reason", { value: input.reason, enumerable: true });
29
+ if (input.detail !== undefined) Object.defineProperty(error, "detail", { value: input.detail, enumerable: true });
30
+ Object.defineProperty(error, FAILURE, { value: true });
31
+ return error;
32
+ }
33
+
34
+ /** Refuse an input that is not a Failure's. A defect of the Machine, not a Failure: it ends the run
35
+ * `error`, with this text as the fault. */
36
+ function checked(raw: unknown): FailureInput {
37
+ const input = raw as Partial<FailureInput> | undefined;
38
+ if (typeof input !== "object" || input === null || typeof input.reason !== "string" || input.reason === "") {
39
+ throw new Error(
40
+ `fail: the input must be { reason: string; detail?: unknown } with a reason that is not empty (got ${describe(raw)}) — ` +
41
+ `invoke: { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)`,
42
+ );
43
+ }
44
+ return input.detail === undefined ? { reason: input.reason } : { reason: input.reason, detail: input.detail };
45
+ }
46
+
47
+ function describe(value: unknown): string {
48
+ try {
49
+ return JSON.stringify(value) ?? String(value);
50
+ } catch {
51
+ return String(value);
52
+ }
53
+ }
54
+
55
+ /** The actor logic that throws. Unbranded: what `workspace()` invokes to send a caught Failure up
56
+ * again, which is the wrapper's mechanism and no state of the author's that fails the run. */
57
+ export const failLogic: PromiseActorLogic<never, FailureInput> = fromPromise<never, FailureInput>(async ({ input }) => {
58
+ throw failure(checked(input));
59
+ });
60
+
61
+ /** The logic `fail()` returns: {@link failLogic} with the brand the walk reads. */
62
+ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL_SLOT]: true };
63
+
64
+ /**
65
+ * Fail the run: the Part a Machine invokes to end work that did not succeed. Put it in `actors`
66
+ * under a name, and invoke it from any state, at any depth: `actors: { fail: fail() }`, then
67
+ * `invoke: { src: "fail", input: { reason } }`.
68
+ *
69
+ * The input is `{ reason: string; detail?: unknown }`. `reason` is text for a person, and it must
70
+ * not be empty. `detail` is optional JSON for a machine reader.
71
+ *
72
+ * The Failure goes up from the state that invokes `fail`, through each parent Machine, as an error
73
+ * does. An `onError` on the way catches it; {@link isFailure} tells it from a defect. When no state
74
+ * catches it, the run ends `failed`: its output is `{ reason, detail }`, and `jr2 run` exits 1. A
75
+ * `workspace()` that a Failure goes through deletes its Sandbox first.
76
+ *
77
+ * To fail, invoke `fail`. Do not throw from a promise to fail: a throw is a defect, and the run
78
+ * ends `error`. A final state means `done`: do not use one for work that did not succeed. To keep
79
+ * the work for a person to read, wait at a Gate before the state that fails.
80
+ *
81
+ * It refuses an input that is not a Failure's, and the run ends `error`: ``fail: the input must be
82
+ * { reason: string; detail?: unknown } with a reason that is not empty (got <value>) — invoke:
83
+ * { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)``.
84
+ *
85
+ * @returns The actor logic of the slot.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { z } from "zod";
90
+ * import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
91
+ *
92
+ * const runInput = z.object({ branch: z.string() });
93
+ *
94
+ * const body = jr2Setup({
95
+ * types: {} as { input: Workspaced<z.infer<typeof runInput>, "app"> },
96
+ * actors: {
97
+ * tests: command(["npm", "test"], { timeout: "15m" }),
98
+ * fail: fail(),
99
+ * },
100
+ * }).createMachine({
101
+ * id: "check",
102
+ * on: { "workspace.lost": ".lost" },
103
+ * initial: "testing",
104
+ * states: {
105
+ * testing: {
106
+ * invoke: {
107
+ * src: "tests",
108
+ * onDone: [
109
+ * {
110
+ * guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
111
+ * target: "passed",
112
+ * },
113
+ * { target: "failing" },
114
+ * ],
115
+ * },
116
+ * },
117
+ * // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
118
+ * failing: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
119
+ * passed: { type: "final" },
120
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
121
+ * },
122
+ * });
123
+ *
124
+ * export const machine = workspace(body, {
125
+ * input: runInput,
126
+ * repos: { app: "https://github.com/you/app.git" },
127
+ * spec: ({ input }) => ({ branch: input.branch }),
128
+ * });
129
+ * ```
130
+ *
131
+ * @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
132
+ */
133
+ export function fail(): FailSlot {
134
+ // Logic of its own per slot, so the brand is on this slot's logic and on no other's.
135
+ const logic = fromPromise<never, FailureInput>(async ({ input }) => {
136
+ throw failure(checked(input));
137
+ });
138
+ return Object.defineProperty(logic, FAIL_SLOT, { value: true }) as FailSlot;
139
+ }
140
+
141
+ /** Is this actor logic a `fail()` slot? A test on the brand, like `isAgent`: what the walk reads to
142
+ * list each state that can fail the run (ADR-0073). */
143
+ export function isFailSlot(logic: unknown): logic is FailSlot {
144
+ return typeof logic === "object" && logic !== null && (logic as { [FAIL_SLOT]?: unknown })[FAIL_SLOT] === true;
145
+ }
146
+
147
+ /**
148
+ * Is this error a Failure, which a state invoked `fail` to make? A defect — any other error — is
149
+ * not. Use it in the guard of an `onError` to catch a Failure and let a defect go up, and read the
150
+ * Failure's `reason` and `detail` from the error it narrows.
151
+ *
152
+ * @param error - The error of an error event: `event.error` in an `onError`.
153
+ * @returns True for a Failure, with `error` narrowed to `{ reason, detail? }`.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * import { z } from "zod";
158
+ * import { defineEvent, fail, isFailure, jr2Setup } from "@jr2/orchestrator";
159
+ *
160
+ * const retry = defineEvent({ name: "retry", description: "Try the plan again.", input: z.object({}) });
161
+ *
162
+ * const attempt = jr2Setup({ actors: { fail: fail() } }).createMachine({
163
+ * id: "attempt",
164
+ * initial: "stuck",
165
+ * states: { stuck: { invoke: { src: "fail", input: { reason: "no plan fits the ticket" } } } },
166
+ * });
167
+ *
168
+ * export const machine = jr2Setup({ events: [retry], actors: { attempt } }).createMachine({
169
+ * id: "planner",
170
+ * initial: "planning",
171
+ * states: {
172
+ * planning: {
173
+ * invoke: {
174
+ * src: "attempt",
175
+ * onDone: "planned",
176
+ * // A Failure waits for a person; a defect has no route, so it goes up and the run ends `error`.
177
+ * onError: { guard: ({ event }) => isFailure(event.error), target: "asking" },
178
+ * },
179
+ * },
180
+ * asking: { invoke: { src: "gate" }, on: { retry: "planning" } },
181
+ * planned: { type: "final" },
182
+ * },
183
+ * });
184
+ * ```
185
+ *
186
+ * @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
187
+ */
188
+ export function isFailure(error: unknown): error is Failure {
189
+ return (
190
+ typeof error === "object" &&
191
+ error !== null &&
192
+ (error as { [FAILURE]?: unknown })[FAILURE] === true &&
193
+ typeof (error as { reason?: unknown }).reason === "string"
194
+ );
195
+ }
196
+
197
+ /** The name of the guard that every Chart has (ADR-0070, ADR-0073): {@link isFailure} on the
198
+ * event's `error`, so a Chart's `onError` can tell a Failure from a defect. It is a predicate and
199
+ * gives no capability, so it is never lent, as `jr2.sentBy` is not. */
200
+ export const FAILURE_GUARD = "jr2.failure";
201
+
202
+ /** The implementation of {@link FAILURE_GUARD}: true when the event's `error` is a Failure. */
203
+ export const failureGuard = ({ event }: { event: { error?: unknown } }): boolean => isFailure(event.error);
204
+
205
+ /** A Failure's payload as plain JSON data: what a failed run's output is, and what `workspace()`
206
+ * keeps to send it up again. */
207
+ export function failurePayload(error: Failure): FailureInput {
208
+ return error.detail === undefined ? { reason: error.reason } : { reason: error.reason, detail: error.detail };
209
+ }