@jr2/orchestrator 0.5.1 → 0.7.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 (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -14,6 +14,7 @@
14
14
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
15
15
  import { lookup } from "node:dns/promises";
16
16
  import { z } from "zod";
17
+ import type { LaterOutcome } from "./effect.ts";
17
18
 
18
19
  // --- The request (POST /effects/<id>) ------------------------------------------------------------
19
20
  //
@@ -104,10 +105,10 @@ export const PairShape = z.strictObject({
104
105
  });
105
106
  export type Pair = z.infer<typeof PairShape>;
106
107
 
107
- /** A `push` start: the Workspace's branch to the Repo Slot's Binding, with the push credential of
108
+ /** A `push` start: a cut Worktree's branch to the Repo Slot's Binding, with the push credential of
108
109
  * the `git.credentials` entry the url falls under — the Effector's config holds the entries, and
109
110
  * matches the url as the fence does (`credential` names one by its `match` instead). `pin` is the
110
- * commit; absent, the Effector reads the Agent's branch tip and its answer carries it (`pinned`).
111
+ * commit; absent, the Effector reads the branch's tip and its answer carries it (`pinned`).
111
112
  */
112
113
  export const PushStartShape = z.strictObject({
113
114
  kind: z.literal("push"),
@@ -181,37 +182,90 @@ export const DispatchedByShape = z.strictObject({
181
182
  * A `dispatch` start (ADR-0071), which only the Instance Effector serves: a run of `workflow` on the
182
183
  * Orchestrator at `url` (its origin, one text part), the body `input` whole, the Instance token as a
183
184
  * 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
+ * caller is foreign by construction — `onBehalfOf` as the `jr2-on-behalf-of` header, and, with
186
+ * `wait`, a poll of `GET /runs/<id>` until the run
185
187
  * ends or `timeoutMs` passes.
188
+ *
189
+ * `keyOnly` in place of `input`: a key-only ask, sent when the slot's schema refuses the body. The
190
+ * Effector sends the `jr2-key-only` header and no body; the door answers the run under the key, or
191
+ * `{ existing: false }`, which the Effector settles as `absent`. One of the two is set, never both.
186
192
  */
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
193
+ export const DispatchStartShape = z
194
+ .strictObject({
195
+ kind: z.literal("dispatch"),
196
+ url: TextShape.refine(
197
+ (parts) => parts.length === 1 && "text" in parts[0]! && ORIGIN.test(parts[0].text),
198
+ "a dispatch url is one text part, an origin: http(s)://host[:port]",
199
+ ),
200
+ workflow: z.string().regex(WORKFLOW_NAME),
201
+ input: z.record(z.string(), z.unknown()).optional(),
202
+ keyOnly: z.literal(true).optional(),
203
+ auth: z.strictObject({ bearer: SecretRefShape }),
204
+ // Every id non-empty, as the door's header shape requires (http.ts): an empty one would be the
205
+ // door's 400, which reads as a defect of the input. The effect is a ledger row id, the key. The Go
206
+ // side's `validateDispatch` holds the same rule.
207
+ dispatchedBy: DispatchedByShape.extend({
208
+ run: z.string().min(1),
209
+ slot: z.string().min(1),
210
+ effect: z
211
+ .string()
212
+ .regex(/^[A-Za-z0-9._-]{1,128}$/)
213
+ .refine((id) => id !== "." && id !== ".."),
214
+ instance: z.string().min(1),
215
+ }),
216
+ wait: z.boolean(),
217
+ timeoutMs: z.number().int().positive(),
218
+ // The first Caller of the calling run's chain (ADR-0071, ADR-0078), sent as the
219
+ // `jr2-on-behalf-of` header: printable ASCII, as a header value is, and at most what the door
220
+ // takes. The other Instance records it and does not check it.
221
+ onBehalfOf: z
203
222
  .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
- });
223
+ .regex(/^[\x20-\x7e]{1,256}$/)
224
+ .optional(),
225
+ })
226
+ .refine(
227
+ (req) => (req.input === undefined) !== (req.keyOnly === undefined),
228
+ "a dispatch start carries its input, or keyOnly and no input",
229
+ );
211
230
  export type DispatchStartRequest = z.infer<typeof DispatchStartShape>;
212
231
 
213
- /** Every start: the http kinds, a push, a credentialed command, a dispatch. */
214
- export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape, DispatchStartShape]);
232
+ /** An MCP server's name (ADR-0079), as the `mcp` block of `jr2.config.ts` keys it. The Go side's
233
+ * `mcpServer` is the same rule. */
234
+ export const MCP_SERVER = /^[a-z][a-z0-9-]*$/;
235
+
236
+ /**
237
+ * An `mcp` start (ADR-0067, ADR-0079), which only the Instance Effector serves: the server by its
238
+ * name — the Effector reads its url and the name of its secret from `effector.json`, so neither is
239
+ * on the wire — and one request: `call` a tool with its arguments, `read` a resource, or get a
240
+ * `prompt` with its arguments. `timeoutMs` is the author's; absent, the server's `timeout` from
241
+ * `effector.json` applies. `maxBody` bounds the answer's `text` and its `structured` value.
242
+ */
243
+ export const McpStartShape = z.strictObject({
244
+ kind: z.literal("mcp"),
245
+ mcp: z
246
+ .strictObject({
247
+ server: z.string().regex(MCP_SERVER),
248
+ call: z.strictObject({ tool: z.string().min(1), arguments: z.record(z.string(), z.unknown()) }).optional(),
249
+ read: z.strictObject({ uri: z.string().min(1) }).optional(),
250
+ prompt: z.strictObject({ name: z.string().min(1), arguments: z.record(z.string(), z.string()) }).optional(),
251
+ })
252
+ .refine(
253
+ (m) => [m.call, m.read, m.prompt].filter((x) => x !== undefined).length === 1,
254
+ "an mcp start carries exactly one of call, read, prompt",
255
+ ),
256
+ timeoutMs: z.number().int().positive().optional(),
257
+ maxBody: z.number().int().positive(),
258
+ });
259
+ export type McpStartRequest = z.infer<typeof McpStartShape>;
260
+
261
+ /** Every start: the http kinds, a push, a credentialed command, a dispatch, an mcp request. */
262
+ export const StartRequestShape = z.union([
263
+ HttpStartShape,
264
+ PushStartShape,
265
+ CommandStartShape,
266
+ DispatchStartShape,
267
+ McpStartShape,
268
+ ]);
215
269
  export type StartRequest = z.infer<typeof StartRequestShape>;
216
270
 
217
271
  /** The most bytes of a start's body the Effector reads (its `maxRequestBody`). A longer body
@@ -259,6 +313,8 @@ export const HttpOutputShape = z.union([
259
313
  * - `unknown`: the call can have gone out, but no answer came, or jr2 cannot tell after a restart.
260
314
  * `reason` tells why. jr2 does not send it again unless it is marked `idempotent`.
261
315
  *
316
+ * The union is open (`LaterOutcome`): give the rest a last arm with no guard.
317
+ *
262
318
  * @example
263
319
  * ```ts
264
320
  * import { fail, http, jr2Setup, type HttpOutput } from "@jr2/orchestrator";
@@ -291,7 +347,7 @@ export const HttpOutputShape = z.union([
291
347
  * @see docs/guides/run-a-command-or-call-a-url.md
292
348
  * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
293
349
  */
294
- export type HttpOutput = z.infer<typeof HttpOutputShape>;
350
+ export type HttpOutput = z.infer<typeof HttpOutputShape> | LaterOutcome;
295
351
 
296
352
  /**
297
353
  * A `push` Effect's output (ADR-0067). `pushed`: the published commit, its source (the Agent's
@@ -376,9 +432,92 @@ export const DispatchOutputShape = z.discriminatedUnion("outcome", [
376
432
  z.strictObject({ outcome: z.literal("refused"), status: z.number().int(), report: z.array(z.string()) }),
377
433
  z.strictObject({ outcome: z.literal("unreachable"), reason: z.string() }),
378
434
  z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
435
+ // The answer to a key-only ask when no run has the key (ADR-0071): nothing was started. Wire
436
+ // only: the driver makes it the invoke's error, the slot's schema refusal, and the Machine never
437
+ // reads it.
438
+ z.strictObject({ outcome: z.literal("absent") }),
379
439
  ]);
380
440
  export type DispatchWireOutput = z.infer<typeof DispatchOutputShape>;
381
441
 
442
+ /**
443
+ * An `mcp` Effect's output on the wire (ADR-0067, ADR-0079): `returned`, with `isError` for a call
444
+ * alone; or a reason for `refused`, `unreachable`, `input-required` and `unknown`.
445
+ */
446
+ export const McpOutputShape = z.union([
447
+ z.strictObject({
448
+ outcome: z.literal("returned"),
449
+ isError: z.boolean().optional(),
450
+ text: z.string(),
451
+ structured: z.unknown().optional(),
452
+ truncated: z.boolean().optional(),
453
+ }),
454
+ z.strictObject({
455
+ outcome: z.enum(["refused", "unreachable", "input-required", "unknown"]),
456
+ reason: z.string(),
457
+ }),
458
+ ]);
459
+
460
+ /** The outcomes that an `mcp` Effect gives besides `returned`. Each one has a `reason` in words. */
461
+ type McpNotReturned =
462
+ | { outcome: "refused"; reason: string }
463
+ | { outcome: "unreachable"; reason: string }
464
+ | { outcome: "input-required"; reason: string }
465
+ | { outcome: "unknown"; reason: string }
466
+ | LaterOutcome;
467
+
468
+ /**
469
+ * The output of an `mcp.call` Effect: what the `onDone` of its state gets as `event.output`. Every
470
+ * outcome is output. Route on `outcome`:
471
+ *
472
+ * - `returned`: the server answered the call. `isError` is the tool's own `isError`: the tool ran
473
+ * and says that it failed. `text` joins the text of the result's content; a part that is not text
474
+ * is one line that names its type. `structured` is the result's `structuredContent`, when it has
475
+ * one. `text` and `structured` each have the byte bound of the Effect's `maxBody`: past it, jr2
476
+ * cuts `text`, drops `structured`, and sets `truncated`.
477
+ * - `refused`: the request did not run. The server answered an HTTP 3xx or 4xx status (a 401 or a
478
+ * 403 included) at any step, the handshake too; it refused the handshake; or it answered the
479
+ * request with the JSON-RPC error `-32700`, `-32600`, `-32601` or `-32602`: a request that it
480
+ * could not read, an unknown method, an unknown tool, or bad arguments; or it answered a read with
481
+ * `-32002`: the resource does not exist. It is also the Effector's
482
+ * own refusal, with nothing sent: the server or its secret is not in its `effector.json`.
483
+ * - `unreachable`: nothing of the request was sent: the name did not resolve, or the connection,
484
+ * TLS or the handshake failed otherwise (a 5xx, the timeout, an answer that is not MCP's, a
485
+ * revision of MCP that jr2 does not speak).
486
+ * - `input-required`: the server asked for input, which jr2 does not give. After the error
487
+ * `-32042`, the tool did not run. After an `elicitation/create` request, which comes while the
488
+ * tool runs, the tool may have begun. jr2 does not send the request again: a Machine that
489
+ * invokes the Effect again after a Gate can run a part of the tool two times.
490
+ * - `unknown`: the request was sent and no answer came (the timeout, a reset, a stream that ended
491
+ * first), or the server answered with a 5xx or another JSON-RPC error. The tool may have acted.
492
+ * jr2 does not send it again unless it is marked `idempotent`.
493
+ *
494
+ * jr2 replaces each value of an Effect secret that the Effector holds with `[secret:<name>]`, in
495
+ * every string of the output. The union is open ({@link LaterOutcome}): give the rest a last arm
496
+ * with no guard.
497
+ *
498
+ * @see docs/concepts/effects.md
499
+ * @see docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md
500
+ */
501
+ export type McpCallOutput =
502
+ | { outcome: "returned"; isError: boolean; text: string; structured?: unknown; truncated?: boolean }
503
+ | McpNotReturned;
504
+
505
+ /**
506
+ * The output of an `mcp.read` or an `mcp.prompt` Effect. Its outcomes are those of
507
+ * {@link McpCallOutput}, with no `isError`. For a read, `text` joins the text of the resource's
508
+ * contents, and `structured` is the `contents` list: a blob is given only as its `mimeType` and its
509
+ * size. For a prompt, `text` joins the text of the prompt's messages, and `structured` is the
510
+ * `messages` list. Past the bound, `structured` is dropped and `truncated` is set.
511
+ *
512
+ * @see docs/concepts/effects.md
513
+ */
514
+ export type McpReadOutput =
515
+ | { outcome: "returned"; text: string; structured?: unknown; truncated?: boolean }
516
+ | McpNotReturned;
517
+
518
+ /** The output of any `mcp` Effect: a call's, a read's or a prompt's. */
519
+ export type McpOutput = McpCallOutput | McpReadOutput;
520
+
382
521
  /** The replica that holds an Effect, and where to poll it. */
383
522
  export const ReplicaShape = z.strictObject({ name: z.string(), address: z.string() });
384
523
  export type Replica = z.infer<typeof ReplicaShape>;
@@ -394,13 +533,122 @@ export const EffectStateShape = z.strictObject({
394
533
  /** The run a dispatch started, once the door answered and while its wait runs: the Orchestrator
395
534
  * writes it to the ledger's intent row, so the caller names its callee during the wait (ADR-0071). */
396
535
  run: z.string().optional(),
397
- output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape]).optional(),
536
+ output: z
537
+ .union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape, McpOutputShape])
538
+ .optional(),
398
539
  });
399
540
  export type EffectState = z.infer<typeof EffectStateShape>;
400
541
 
401
542
  /** A refusal's body: the reason, and the replica when it names one. */
402
543
  export const RefusalShape = z.strictObject({ error: z.string(), replica: ReplicaShape.optional() });
403
544
 
545
+ // --- The `mac` check (POST /mac, ADR-0068, ADR-0078) ---------------------------------------------
546
+ //
547
+ // A Hook's `mac` verifier: the Orchestrator builds the bytes that the service signed, and the
548
+ // Instance Effector, which holds the secret, computes the HMAC over them and compares it with the
549
+ // signature in constant time. The answer is "match" or "no match", and nothing more: no HMAC and no
550
+ // secret ever comes back. The Orchestrator makes every check that needs no secret (the window)
551
+ // before it asks.
552
+
553
+ /** The HMAC algorithms of a `mac` check. */
554
+ export const MAC_ALGS = ["sha256", "sha1", "sha512"] as const;
555
+
556
+ /** A `mac` check: the NAME of the secret, the algorithm, the signed bytes and the signature, each
557
+ * in base64. */
558
+ export const MacRequestShape = z.strictObject({
559
+ // A secret's name, as the Effector reads it: a file under its secrets mount.
560
+ secret: z
561
+ .string()
562
+ .regex(/^[A-Za-z0-9._-]{1,253}$/)
563
+ .refine((name) => !name.startsWith("."), "a secret's name does not start with ."),
564
+ alg: z.enum(MAC_ALGS),
565
+ bytes: z.base64(),
566
+ signature: z.base64(),
567
+ });
568
+ export type MacRequest = z.infer<typeof MacRequestShape>;
569
+
570
+ /** The answer to a `mac` check: whether the HMAC of the bytes with the secret is the signature. */
571
+ export const MacAnswerShape = z.strictObject({ match: z.boolean() });
572
+ export type MacAnswer = z.infer<typeof MacAnswerShape>;
573
+
574
+ /** The path of a `mac` check on the Instance Effector. */
575
+ export const MAC_PATH = "/mac";
576
+
577
+ /** The `mac` check, as the Hook's door asks it: `true` for a match. It rejects when no Effector can
578
+ * answer, and the door then answers 503. */
579
+ export type HookMac = (req: {
580
+ secret: string;
581
+ alg: (typeof MAC_ALGS)[number];
582
+ bytes: Uint8Array;
583
+ signature: Uint8Array;
584
+ }) => Promise<boolean>;
585
+
586
+ /**
587
+ * The `mac` check over the Instance Effector's signed wire (ADR-0068): one replica, any one, since
588
+ * each holds the same secrets. A replica that was never reached is passed over for the next. An
589
+ * answer is trusted only with the Effector's signature, as an Effect's is.
590
+ */
591
+ export function createMacClient(opts: {
592
+ key: Buffer | string;
593
+ transport: EffectorTransport;
594
+ now?: () => number;
595
+ }): HookMac {
596
+ const now = opts.now ?? Date.now;
597
+ return async ({ secret, alg, bytes, signature }) => {
598
+ const body = JSON.stringify({
599
+ secret,
600
+ alg,
601
+ bytes: Buffer.from(bytes).toString("base64"),
602
+ signature: Buffer.from(signature).toString("base64"),
603
+ } satisfies MacRequest);
604
+ const addresses = shuffle(await opts.transport.replicas().catch(notSent));
605
+ if (addresses.length === 0) throw new EffectorNotSent("no Effector replica is serving");
606
+ let last: unknown;
607
+ for (const address of addresses) {
608
+ const time = String(now());
609
+ const nonce = randomBytes(16).toString("hex");
610
+ const headers = {
611
+ "content-type": "application/json",
612
+ [HEADER_TIME]: time,
613
+ [HEADER_NONCE]: nonce,
614
+ [HEADER_SIGNATURE]: requestMac(opts.key, "POST", address, MAC_PATH, time, nonce, body),
615
+ };
616
+ let answer: RawAnswer;
617
+ try {
618
+ answer = await opts.transport.send(address, "POST", MAC_PATH, headers, body);
619
+ } catch (err) {
620
+ if (neverReached(err)) {
621
+ last = err;
622
+ continue;
623
+ }
624
+ throw err;
625
+ }
626
+ const want = responseMac(opts.key, answer.status, "POST", MAC_PATH, nonce, answer.body);
627
+ if (!sameString(answer.headers[HEADER_SIGNATURE] ?? "", want)) {
628
+ throw new EffectorForged(
629
+ `the answer to POST ${MAC_PATH} from ${address} does not carry the Effector's signature`,
630
+ );
631
+ }
632
+ if (answer.status === 503) {
633
+ last = new EffectorNotSent(`the Effector replica at ${address} is stopping`);
634
+ continue;
635
+ }
636
+ if (answer.status !== 200) {
637
+ const refusal = RefusalShape.safeParse(parseJson(answer.body));
638
+ throw new Error(
639
+ `the Effector answered a mac check with ${answer.status}: ${refusal.success ? refusal.data.error : "no reason"}`,
640
+ );
641
+ }
642
+ const parsed = MacAnswerShape.safeParse(parseJson(answer.body));
643
+ if (!parsed.success) throw new EffectorMalformed("the Effector's answer to a mac check is not the wire's shape");
644
+ return parsed.data.match;
645
+ }
646
+ throw last instanceof EffectorNotSent
647
+ ? last
648
+ : new EffectorNotSent(`no Effector replica answered: ${(last as Error | undefined)?.message ?? "none"}`);
649
+ };
650
+ }
651
+
404
652
  // --- Signatures ----------------------------------------------------------------------------------
405
653
 
406
654
  export const HEADER_TIME = "x-jr2-time";
package/src/fail.ts CHANGED
@@ -87,24 +87,37 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
87
87
  * @example
88
88
  * ```ts
89
89
  * import { z } from "zod";
90
- * import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
90
+ * import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
91
91
  *
92
92
  * const runInput = z.object({ branch: z.string() });
93
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
94
+ *
95
+ * const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
93
96
  *
94
97
  * const body = jr2Setup({
95
- * types: {} as { input: Workspaced<z.infer<typeof runInput>, "app"> },
98
+ * types: {} as { input: BodyInput; context: BodyInput },
96
99
  * actors: {
100
+ * cut: worktree(),
97
101
  * tests: command(["npm", "test"], { timeout: "15m" }),
98
102
  * fail: fail(),
99
103
  * },
100
104
  * }).createMachine({
101
105
  * id: "check",
106
+ * context: ({ input }) => input,
102
107
  * on: { "workspace.lost": ".lost" },
103
- * initial: "testing",
108
+ * initial: "cutting",
104
109
  * states: {
110
+ * cutting: {
111
+ * invoke: {
112
+ * src: "cut",
113
+ * input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
114
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
115
+ * },
116
+ * },
105
117
  * testing: {
106
118
  * invoke: {
107
119
  * src: "tests",
120
+ * input: ({ context }) => ({ worktree: testTree(context) }),
108
121
  * onDone: [
109
122
  * {
110
123
  * guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
@@ -117,6 +130,7 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
117
130
  * // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
118
131
  * failing: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
119
132
  * passed: { type: "final" },
133
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
120
134
  * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
121
135
  * },
122
136
  * });
@@ -124,7 +138,6 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
124
138
  * export const machine = workspace(body, {
125
139
  * input: runInput,
126
140
  * repos: { app: "https://github.com/you/app.git" },
127
- * spec: ({ input }) => ({ branch: input.branch }),
128
141
  * });
129
142
  * ```
130
143
  *
@@ -0,0 +1,16 @@
1
+ // The brand of a Gate slot (ADR-0011), alone in its module: the walk (setup.ts, party.ts,
2
+ // effect-checks.ts) and the Chart door read it, and gate.ts imports the registration table, which
3
+ // imports party.ts. A leaf with no imports lets each of them ask without a cycle.
4
+
5
+ /** Is this actor logic a Gate slot (`gate({ … })`)? A duck test on the brand, as `isAgent` is. */
6
+ export function isGateSlotLogic(logic: unknown): boolean {
7
+ if (typeof logic !== "object" || logic === null) return false;
8
+ const parts = (logic as { gateSlot?: { delivers?: unknown } }).gateSlot;
9
+ return typeof parts === "object" && parts !== null && Array.isArray(parts.delivers);
10
+ }
11
+
12
+ /** Does this invoke run a Gate: the plain `gate`, or a Gate slot? */
13
+ export function isGateInvoke(inv: { src?: unknown } | undefined, actors: Record<string, unknown>): boolean {
14
+ if (inv?.src === "gate") return true;
15
+ return typeof inv?.src === "string" && isGateSlotLogic(actors[inv.src]);
16
+ }