@jr2/orchestrator 0.3.0 → 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 (354) hide show
  1. package/README.md +20 -2
  2. package/bin/server.ts +7 -4
  3. package/console/canvas.ts +27 -14
  4. package/console/components/app.ts +12 -1
  5. package/console/components/fleet.ts +52 -6
  6. package/console/components/machine-pane.ts +128 -2
  7. package/console/components/nav.ts +1 -1
  8. package/console/components/schema-form.ts +12 -4
  9. package/console/json-field.ts +23 -0
  10. package/console/main.ts +122 -39
  11. package/console/store.ts +321 -21
  12. package/console/style.css +111 -0
  13. package/docs/agents-block.md +5 -0
  14. package/docs/concepts/agents-and-turns.md +442 -0
  15. package/docs/concepts/architecture.md +230 -0
  16. package/docs/concepts/charts.md +143 -0
  17. package/docs/concepts/composition.md +357 -0
  18. package/docs/concepts/converge.md +254 -0
  19. package/docs/concepts/durable-runs.md +189 -0
  20. package/docs/concepts/effects.md +603 -0
  21. package/docs/concepts/gates.md +208 -0
  22. package/docs/concepts/machines-and-workflows.md +289 -0
  23. package/docs/concepts/pools-and-sources.md +230 -0
  24. package/docs/concepts/secrets-and-trust.md +222 -0
  25. package/docs/concepts/the-menu.md +378 -0
  26. package/docs/concepts/workspaces.md +678 -0
  27. package/docs/get-started/a-model-decides.md +563 -0
  28. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  29. package/docs/get-started/connect-a-model.md +248 -0
  30. package/docs/get-started/create-an-instance.md +194 -0
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  32. package/docs/get-started/install.md +126 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  34. package/docs/guides/answer-a-gate.md +304 -0
  35. package/docs/guides/bind-open-parts.md +223 -0
  36. package/docs/guides/cancel-a-run.md +146 -0
  37. package/docs/guides/clean-up.md +206 -0
  38. package/docs/guides/fix-a-failed-converge.md +361 -0
  39. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  41. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  42. package/docs/guides/look-inside-a-sandbox.md +174 -0
  43. package/docs/guides/mirror-the-kit-images.md +176 -0
  44. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  45. package/docs/guides/open-the-console.md +91 -0
  46. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  47. package/docs/guides/push-and-sign-a-branch.md +486 -0
  48. package/docs/guides/run-a-chart-by-name.md +270 -0
  49. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  50. package/docs/guides/run-one-worker-per-item.md +321 -0
  51. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  52. package/docs/guides/size-a-workspace.md +204 -0
  53. package/docs/guides/start-a-run-from-a-run.md +237 -0
  54. package/docs/guides/start-a-run.md +207 -0
  55. package/docs/guides/upgrade-the-kit-version.md +240 -0
  56. package/docs/guides/use-a-hosted-provider.md +208 -0
  57. package/docs/guides/use-a-local-model.md +313 -0
  58. package/docs/guides/watch-a-run.md +326 -0
  59. package/docs/guides/write-a-workflow.md +318 -0
  60. package/docs/index.md +44 -0
  61. package/docs/llms.txt +216 -0
  62. package/docs/reference/authoring-surface/agent.md +128 -0
  63. package/docs/reference/authoring-surface/answer.md +140 -0
  64. package/docs/reference/authoring-surface/arg.md +60 -0
  65. package/docs/reference/authoring-surface/basic.md +62 -0
  66. package/docs/reference/authoring-surface/bearer.md +53 -0
  67. package/docs/reference/authoring-surface/chartschema.md +23 -0
  68. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  69. package/docs/reference/authoring-surface/command.md +191 -0
  70. package/docs/reference/authoring-surface/customize.md +64 -0
  71. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  72. package/docs/reference/authoring-surface/defineevent.md +114 -0
  73. package/docs/reference/authoring-surface/dispatch.md +126 -0
  74. package/docs/reference/authoring-surface/fail.md +83 -0
  75. package/docs/reference/authoring-surface/file.md +122 -0
  76. package/docs/reference/authoring-surface/frompick.md +277 -0
  77. package/docs/reference/authoring-surface/http.md +159 -0
  78. package/docs/reference/authoring-surface/isfailure.md +58 -0
  79. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  80. package/docs/reference/authoring-surface/open.md +74 -0
  81. package/docs/reference/authoring-surface/openbody.md +86 -0
  82. package/docs/reference/authoring-surface/paramsof.md +125 -0
  83. package/docs/reference/authoring-surface/pool.md +186 -0
  84. package/docs/reference/authoring-surface/push.md +112 -0
  85. package/docs/reference/authoring-surface/secret.md +118 -0
  86. package/docs/reference/authoring-surface/sentby.md +83 -0
  87. package/docs/reference/authoring-surface/source.md +51 -0
  88. package/docs/reference/authoring-surface/text.md +62 -0
  89. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  90. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  91. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  92. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  93. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  94. package/docs/reference/authoring-surface/type-arg.md +19 -0
  95. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  96. package/docs/reference/authoring-surface/type-binding.md +62 -0
  97. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  98. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  99. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  100. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  101. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  102. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  103. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  104. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  105. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  106. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  107. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  108. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  109. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  110. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  111. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  112. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  113. package/docs/reference/authoring-surface/type-childat.md +12 -0
  114. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  115. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  116. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  117. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  118. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  119. package/docs/reference/authoring-surface/type-customize.md +109 -0
  120. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  121. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  122. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  124. package/docs/reference/authoring-surface/type-duration.md +15 -0
  125. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  126. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  127. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  128. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  129. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  130. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  131. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  132. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  133. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  134. package/docs/reference/authoring-surface/type-failure.md +19 -0
  135. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  136. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  137. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  138. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  139. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  140. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  141. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  142. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  143. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  144. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  145. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  146. package/docs/reference/authoring-surface/type-lends.md +38 -0
  147. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  148. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  149. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  150. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  151. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  152. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  153. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  154. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  155. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  156. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  157. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  158. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  159. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  160. package/docs/reference/authoring-surface/type-openof.md +12 -0
  161. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  162. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  163. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  164. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  165. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  166. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  167. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  168. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  169. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  170. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  171. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  172. package/docs/reference/authoring-surface/type-reached.md +12 -0
  173. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  174. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  175. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  176. package/docs/reference/authoring-surface/type-secret.md +25 -0
  177. package/docs/reference/authoring-surface/type-sender.md +19 -0
  178. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  179. package/docs/reference/authoring-surface/type-size.md +85 -0
  180. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  181. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  182. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  183. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  184. package/docs/reference/authoring-surface/type-text.md +19 -0
  185. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  186. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  187. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  188. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  189. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  190. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  191. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  192. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  193. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  194. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  195. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  196. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  197. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  198. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  199. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  200. package/docs/reference/authoring-surface/workspace.md +259 -0
  201. package/docs/reference/authoring-surface.md +197 -0
  202. package/docs/reference/cli.md +433 -0
  203. package/docs/reference/config.md +756 -0
  204. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  205. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  206. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  207. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  208. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  209. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  210. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  211. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  212. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  213. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  214. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  215. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  216. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  217. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  218. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  219. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  220. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  221. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  222. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  223. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  224. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  225. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  226. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  227. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  228. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  229. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  230. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  231. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  232. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  233. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  234. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  235. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  236. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  237. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  238. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  239. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  240. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  241. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  242. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  243. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  244. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  245. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  246. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  247. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  248. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  249. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  250. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  251. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  252. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  253. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  254. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  255. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  256. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  257. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  258. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  259. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  260. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  261. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  262. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  263. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  264. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  265. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  266. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  267. 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
  268. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  269. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  270. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  271. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  272. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  273. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  274. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  275. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  276. package/docs/reference/decisions.md +83 -0
  277. package/docs/reference/glossary.md +648 -0
  278. package/docs/reference/http-api.md +1348 -0
  279. package/docs/reference/machines.md +190 -0
  280. package/docs/reference/mechanism-events.md +72 -0
  281. package/docs/why.md +206 -0
  282. package/package.json +6 -4
  283. package/src/actor.ts +976 -61
  284. package/src/agent.ts +114 -38
  285. package/src/ambient.ts +4 -1
  286. package/src/assembly-checks.ts +347 -0
  287. package/src/assembly-tar.ts +494 -0
  288. package/src/assembly.ts +721 -0
  289. package/src/cause.ts +234 -0
  290. package/src/chart.ts +2182 -0
  291. package/src/charts.ts +748 -0
  292. package/src/config.ts +520 -162
  293. package/src/content-hash.ts +105 -0
  294. package/src/custodian.lua +6 -5
  295. package/src/custodian.ts +143 -39
  296. package/src/customize.ts +388 -55
  297. package/src/dispatch-effect.ts +945 -0
  298. package/src/door-compat.ts +217 -0
  299. package/src/durability.ts +11 -4
  300. package/src/effect-checks.ts +557 -0
  301. package/src/effect.ts +1202 -0
  302. package/src/effector-wire.ts +674 -0
  303. package/src/fail.ts +209 -0
  304. package/src/fingerprint.ts +4 -3
  305. package/src/gate.ts +8 -0
  306. package/src/git-clone.ts +65 -0
  307. package/src/git-push.ts +336 -0
  308. package/src/harness-client.ts +764 -48
  309. package/src/held-secrets.ts +82 -26
  310. package/src/holds.ts +156 -0
  311. package/src/http-effect.ts +1423 -0
  312. package/src/http.ts +916 -113
  313. package/src/images.ts +47 -21
  314. package/src/index.ts +62 -43
  315. package/src/instance.ts +505 -59
  316. package/src/internal.ts +84 -0
  317. package/src/kube-client.ts +333 -0
  318. package/src/machine-doc.ts +106 -6
  319. package/src/mechanism-routes.ts +168 -0
  320. package/src/model-keys.generated.ts +53 -0
  321. package/src/model-keys.ts +18 -58
  322. package/src/mount-digest.ts +53 -0
  323. package/src/names.ts +66 -8
  324. package/src/notices.ts +141 -0
  325. package/src/open-body.ts +1580 -0
  326. package/src/open-effect.ts +217 -0
  327. package/src/open.ts +59 -6
  328. package/src/parts.ts +709 -41
  329. package/src/party.ts +534 -0
  330. package/src/pick.ts +761 -0
  331. package/src/pool.ts +248 -37
  332. package/src/registration.ts +382 -19
  333. package/src/repo-fetch.ts +60 -85
  334. package/src/repos.ts +65 -75
  335. package/src/rest.ts +133 -0
  336. package/src/run-host.ts +2383 -115
  337. package/src/run-output.ts +38 -0
  338. package/src/sandbox-effect.ts +1126 -0
  339. package/src/sandbox-effector.ts +111 -0
  340. package/src/sandbox-kube.ts +1721 -0
  341. package/src/sandbox-watch.ts +260 -0
  342. package/src/secret-placement.ts +529 -0
  343. package/src/server.ts +284 -84
  344. package/src/setup.ts +819 -117
  345. package/src/size.ts +454 -0
  346. package/src/snapshot-store.ts +115 -20
  347. package/src/stub-harness.ts +49 -16
  348. package/src/sync-client.ts +54 -0
  349. package/src/timers.ts +191 -0
  350. package/src/tokens.ts +35 -4
  351. package/src/vocabulary.ts +135 -15
  352. package/src/wire.ts +238 -10
  353. package/src/workspace.ts +1195 -338
  354. package/src/sandbox-kubectl.ts +0 -1108
package/src/party.ts ADDED
@@ -0,0 +1,534 @@
1
+ // Parties and their writings (ADR-0015, ADR-0029). A party is who may send a workflow event into a
2
+ // Machine: an Agent, through its Menu, or a Gate's caller. Every event a party delivers carries
3
+ // `sender: { party, id }` (ADR-0011) — the Agent's slot, or the Gate's invoke id.
4
+ //
5
+ // What an author writes with it: `guard: sentBy("coder")` on a writing — one `on` key on one state.
6
+ // A NAMED guard with params, `{ type: "jr2.sentBy", params: { id } }`, so the load walk can read it.
7
+ // It is false for an event another party sent, and true for the named party's event and for the
8
+ // Machine's own (no sender). xstate's own rule does the rest: a false guard on the deepest writing
9
+ // lets selection move outward. It can only make a writing DECLINE other senders; it never gives a
10
+ // writing to a party — position does that (`mayTake`).
11
+ //
12
+ // What jr2 computes with it:
13
+ // - `mayTake`: may this sender take this writing? ADR-0015's position rule, per writing.
14
+ // - `takesNow`: an Agent's Allowed picks and a Gate's `acceptsNow`, payload-blind (ADR-0029).
15
+ // - `notTaken`: at delivery, exact — a delivery whose selection holds another party's writing is
16
+ // refused before the Machine sees it.
17
+ // - `partyWarnings`: the load walk's warnings over parties and writings.
18
+
19
+ import type { AnyActorRef, AnyEventObject, AnyMachineSnapshot, AnyStateMachine } from "xstate";
20
+ import { SENDER_KEY, type Sender } from "@jr2/agent-protocol";
21
+ import { isAgent } from "./agent.ts";
22
+ import { vocabularyOf } from "./vocabulary.ts";
23
+
24
+ /** The guard type `sentBy` names: dotted, so no author guard can take the name. */
25
+ export const SENT_BY = "jr2.sentBy";
26
+
27
+ /**
28
+ * A guard that is true for an event that one party sent, and false for an event that another party
29
+ * sent: `guard: sentBy("coder")`. Use it where two parties may send the same event to one state,
30
+ * and the transition must run for one of them only. It is also true for an event that the Machine
31
+ * sends itself, which has no sender.
32
+ *
33
+ * It can only make a transition decline the events of other parties. It never lets a party send
34
+ * an event that the party may not send at that state: the position of the `on` key decides that.
35
+ * When the guard is false, xstate tries the next transition for the event, as for any guard. Use
36
+ * it alone, or inside `and([…])`.
37
+ *
38
+ * `createMachine` refuses an id that is not a party of the Machine: ``machine "<id>":
39
+ * sentBy("<party>") on "<event>" at "<state>" names no party of this Machine (Agent slots:
40
+ * <slots>; Gate invoke ids: <ids>)``
41
+ *
42
+ * @param id - The party: the slot key of an Agent of this Machine, or the invoke `id` of one of its
43
+ * Gates.
44
+ * @returns A named guard object, for a transition's `guard`.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { z } from "zod";
49
+ * import { agent, defineEvent, fail, jr2Setup, sentBy } from "@jr2/orchestrator";
50
+ *
51
+ * const done = defineEvent({
52
+ * name: "done",
53
+ * description: "Call this when the draft is ready for review.",
54
+ * input: z.object({}),
55
+ * });
56
+ *
57
+ * export const machine = jr2Setup({
58
+ * events: [done],
59
+ * actors: {
60
+ * writer: agent({
61
+ * model: "anthropic/claude-haiku-5-5",
62
+ * instructions: "You write release notes.",
63
+ * workspace: "none",
64
+ * }),
65
+ * fail: fail(),
66
+ * },
67
+ * }).createMachine({
68
+ * id: "notes",
69
+ * on: { "agent.fault": ".failed" },
70
+ * initial: "drafting",
71
+ * states: {
72
+ * drafting: {
73
+ * invoke: [
74
+ * { src: "writer", input: { prompt: "Draft the release notes." } },
75
+ * { src: "gate", id: "editor" },
76
+ * ],
77
+ * on: {
78
+ * // The writer's `done` goes to review; the editor's `done` skips it.
79
+ * done: [{ guard: sentBy("writer"), target: "review" }, { target: "published" }],
80
+ * },
81
+ * },
82
+ * review: { invoke: { src: "gate" }, on: { done: "published" } },
83
+ * published: { type: "final" },
84
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
85
+ * },
86
+ * });
87
+ * ```
88
+ *
89
+ * @see docs/concepts/the-menu.md
90
+ * @see docs/concepts/gates.md
91
+ * @see docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md
92
+ */
93
+ export function sentBy<const Id extends string>(id: Id): { type: typeof SENT_BY; params: { id: Id } } {
94
+ // A NAMED guard with params, so the load walk can read it (see the module header). The id is
95
+ // refused at load by `refuseUnknownSentBy` when no Agent slot or Gate invoke id has it.
96
+ return { type: SENT_BY, params: { id } };
97
+ }
98
+
99
+ /** The implementation `jr2Setup` registers under {@link SENT_BY}. */
100
+ export function sentByGuard({ event }: { event: AnyEventObject }, params: { id: string }): boolean {
101
+ const sender = (event as { [SENDER_KEY]?: Sender })[SENDER_KEY];
102
+ return sender === undefined || sender.id === params.id;
103
+ }
104
+
105
+ /**
106
+ * The ids a writing's guard names with `sentBy`, read statically: the guard itself, or a member of
107
+ * xstate's `and([...])` — a conjunct, so the writing declines whatever it declines. Under `or()` or
108
+ * `not()` a `sentBy` says nothing certain about the writing, so it is not read (`opaque`); neither
109
+ * is an author's own guard that reads `event.sender`. Undefined when no `sentBy` is readable.
110
+ */
111
+ export function sentByIds(guard: unknown): { ids: string[]; opaque: boolean } | undefined {
112
+ if (guard && typeof guard === "object" && (guard as { type?: unknown }).type === SENT_BY) {
113
+ return { ids: [String((guard as { params: { id: string } }).params.id)], opaque: false };
114
+ }
115
+ const members = typeof guard === "function" ? (guard as { guards?: unknown }).guards : undefined;
116
+ if (Array.isArray(members)) {
117
+ const named = members.map(sentByIds).filter((m) => m !== undefined);
118
+ if (named.length === 0) return undefined;
119
+ if (guard instanceof Function && guard.name !== "and") return { ids: [], opaque: true };
120
+ return { ids: [...new Set(named.flatMap((m) => m.ids))], opaque: named.some((m) => m.opaque) };
121
+ }
122
+ return undefined;
123
+ }
124
+
125
+ /** Does this guard (one candidate of a writing) decline `senderId`'s events, by its `sentBy`, for
126
+ * sure? `and([sentBy(a), sentBy(b)])` with a ≠ b declines every party. */
127
+ export function declines(guard: unknown, senderId: string): boolean {
128
+ const named = sentByIds(guard);
129
+ if (!named || named.opaque) return false;
130
+ return named.ids.some((id) => id !== senderId);
131
+ }
132
+
133
+ type Node = AnyStateMachine["root"];
134
+
135
+ /** The parties a built state invokes, each as the sender it delivers as. */
136
+ export function partiesAt(node: Node, machine: AnyStateMachine): Sender[] {
137
+ const actors = (machine.implementations.actors ?? {}) as Record<string, unknown>;
138
+ const out: Sender[] = [];
139
+ for (const inv of node.invoke) {
140
+ if (typeof inv.src !== "string") continue;
141
+ if (inv.src === "gate") out.push({ party: "gate", id: inv.id });
142
+ else if (isAgent(actors[inv.src])) out.push({ party: "agent", id: inv.src });
143
+ }
144
+ return out;
145
+ }
146
+
147
+ /** Every built state node, document order. */
148
+ export function allNodes(machine: AnyStateMachine): Node[] {
149
+ const out: Node[] = [];
150
+ const visit = (node: Node): void => {
151
+ out.push(node);
152
+ for (const child of Object.values(node.states)) visit(child);
153
+ };
154
+ visit(machine.root);
155
+ return out;
156
+ }
157
+
158
+ /** The Agent slots and Gate invoke ids of a Machine: every id a `sentBy` may name. */
159
+ export function senderIds(machine: AnyStateMachine): { agents: Set<string>; gates: Set<string> } {
160
+ const agents = new Set<string>();
161
+ const gates = new Set<string>();
162
+ for (const node of allNodes(machine))
163
+ for (const p of partiesAt(node, machine)) (p.party === "agent" ? agents : gates).add(p.id);
164
+ return { agents, gates };
165
+ }
166
+
167
+ /**
168
+ * Refuse a `sentBy` that names no party of this Machine — an Agent slot or a Gate's invoke id — at
169
+ * load, like an unknown event key (ADR-0015): a typo would decline every sender, and the writing
170
+ * would run for none.
171
+ */
172
+ export function refuseUnknownSentBy(machine: AnyStateMachine): void {
173
+ const { agents, gates } = senderIds(machine);
174
+ for (const node of allNodes(machine)) {
175
+ for (const [name, candidates] of node.transitions) {
176
+ for (const candidate of candidates) {
177
+ for (const id of sentByIds((candidate as { guard?: unknown }).guard)?.ids ?? []) {
178
+ if (agents.has(id) || gates.has(id)) continue;
179
+ throw new Error(
180
+ `machine "${machine.id}": sentBy("${id}") on "${name}" at "${node.id}" names no party of this Machine ` +
181
+ `(Agent slots: ${[...agents].join(", ") || "none"}; Gate invoke ids: ${[...gates].join(", ") || "none"})`,
182
+ );
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ const isAncestorOrSelf = (a: Node, b: Node): boolean => {
190
+ for (let n: Node | undefined = b; n; n = n.parent) if (n === a) return true;
191
+ return false;
192
+ };
193
+
194
+ /** The nearest state at or above `node` that invokes a party; undefined when none does. */
195
+ export function ownerOf(node: Node, machine: AnyStateMachine): Node | undefined {
196
+ for (let n: Node | undefined = node; n; n = n.parent) if (partiesAt(n, machine).length) return n;
197
+ return undefined;
198
+ }
199
+
200
+ /**
201
+ * The state whose invoke registered `invokeId` — the seat its registration speaks for. Two states
202
+ * may author one invoke id when they are never active together; the active one is the seat.
203
+ */
204
+ export function seatOf(snapshot: AnyMachineSnapshot, invokeId: string): Node | undefined {
205
+ const machine = snapshot.machine as AnyStateMachine;
206
+ const found = allNodes(machine).filter((node) => node.invoke.some((inv) => inv.id === invokeId));
207
+ if (found.length <= 1) return found[0];
208
+ const active = new Set((snapshot as { _nodes?: Node[] })._nodes ?? []);
209
+ return found.find((node) => active.has(node)) ?? found[0];
210
+ }
211
+
212
+ /**
213
+ * May `sender`, seated at `seat`, take the writing of `type` on `source` (ADR-0015, per writing)?
214
+ *
215
+ * In reach: the seat or a state above it, or a state below it with no party-invoking state between
216
+ * — an Agent's modes, and a Gate's plain children, which the same position rule gives the Gate.
217
+ * And owned: an unset audience gives a writing to the party of the nearest party-invoking state at
218
+ * or above it (none: both parties); an explicit audience gives it, by kind, to every party whose
219
+ * reach holds it. `sentBy` plays no part here: it can decline, never give.
220
+ */
221
+ export function mayTake(machine: AnyStateMachine, sender: Sender, seat: Node, source: Node, type: string): boolean {
222
+ let inReach = isAncestorOrSelf(source, seat);
223
+ if (!inReach && isAncestorOrSelf(seat, source)) {
224
+ inReach = true;
225
+ for (let n: Node | undefined = source; n && n !== seat; n = n.parent)
226
+ if (partiesAt(n, machine).length) inReach = false;
227
+ }
228
+ if (!inReach) return false;
229
+ const audience = vocabularyOf(machine)?.get(type)?.audience;
230
+ if (audience !== undefined)
231
+ return audience === "any" || audience === (sender.party === "agent" ? "agent" : "external");
232
+ const owner = ownerOf(source, machine);
233
+ return owner === undefined || owner === seat;
234
+ }
235
+
236
+ // --- Selection: what xstate would run, payload-blind ---------------------------------------------
237
+ //
238
+ // A copy of xstate 5.32.2's `transitionNode` / `StateNode.next` / `evaluateGuard`, with one change:
239
+ // each guard is asked behind a Proxy, and a guard that read the payload answers UNKNOWN, which
240
+ // passes. `type` and `sender` are not payload. `and` / `or` / `not` are evaluated here over their
241
+ // members in three-valued logic, so `and([payloadGuard, sentBy("other")])` still declines. Pinned
242
+ // against xstate's own `getTransitionData` in `party-xstate.test.ts`: an xstate bump that changes
243
+ // selection fails there first.
244
+
245
+ type Tri = true | false | "unknown";
246
+ type Transition = ReturnType<AnyStateMachine["getTransitionData"]>[number];
247
+
248
+ function evalGuard(
249
+ guard: unknown,
250
+ snapshot: AnyMachineSnapshot,
251
+ event: AnyEventObject,
252
+ read: { payload: boolean },
253
+ ): Tri {
254
+ const machine = snapshot.machine as AnyStateMachine;
255
+ const isInline = typeof guard === "function";
256
+ const key = typeof guard === "string" ? guard : (guard as { type?: string }).type;
257
+ const resolved: unknown = isInline ? guard : (machine.implementations.guards as Record<string, unknown>)[key!];
258
+ if (!isInline && !resolved) throw new Error(`Guard '${key}' is not implemented.`);
259
+ if (typeof resolved !== "function") return evalGuard(resolved, snapshot, event, read);
260
+ const context = snapshot.context;
261
+ const members = (resolved as { guards?: unknown[] }).guards;
262
+ if ("check" in resolved && Array.isArray(members)) {
263
+ const values = members.map((m) => evalGuard(m, snapshot, event, read));
264
+ if (resolved.name === "and") return values.includes(false) ? false : values.includes("unknown") ? "unknown" : true;
265
+ if (resolved.name === "or") return values.includes(true) ? true : values.includes("unknown") ? "unknown" : false;
266
+ if (resolved.name === "not") return values[0] === "unknown" ? "unknown" : !values[0];
267
+ }
268
+ const params =
269
+ isInline || typeof guard === "string" || !("params" in (guard as object))
270
+ ? undefined
271
+ : typeof (guard as { params: unknown }).params === "function"
272
+ ? (guard as { params: (a: unknown) => unknown }).params({ context, event })
273
+ : (guard as { params: unknown }).params;
274
+ const before = read.payload;
275
+ read.payload = false;
276
+ const value =
277
+ "check" in resolved
278
+ ? (resolved as { check: (s: unknown, a: unknown, r: unknown) => boolean }).check(
279
+ snapshot,
280
+ { context, event },
281
+ resolved,
282
+ )
283
+ : (resolved as (a: unknown, p: unknown) => boolean)({ context, event }, params);
284
+ const looked = read.payload;
285
+ read.payload = before || looked;
286
+ return looked ? "unknown" : Boolean(value);
287
+ }
288
+
289
+ /** xstate's `getCandidates` for a name with no dot: the exact writing, then the wildcard. */
290
+ function candidates(node: Node, type: string): Transition[] {
291
+ return [
292
+ ...((node.transitions.get(type) ?? []) as Transition[]),
293
+ ...((node.transitions.get("*") ?? []) as Transition[]),
294
+ ];
295
+ }
296
+
297
+ function next(node: Node, snapshot: AnyMachineSnapshot, event: AnyEventObject, read: { payload: boolean }) {
298
+ for (const candidate of candidates(node, event.type)) {
299
+ const guard = (candidate as { guard?: unknown }).guard;
300
+ if (!guard || evalGuard(guard, snapshot, event, read) !== false) return [candidate];
301
+ }
302
+ return undefined;
303
+ }
304
+
305
+ function select(
306
+ node: Node,
307
+ value: unknown,
308
+ snapshot: AnyMachineSnapshot,
309
+ event: AnyEventObject,
310
+ read: { payload: boolean },
311
+ ): Transition[] | undefined {
312
+ if (typeof value === "string") {
313
+ const inner = next(node.states[value]!, snapshot, event, read);
314
+ return inner?.length ? inner : next(node, snapshot, event, read);
315
+ }
316
+ const keys = Object.keys(value as object);
317
+ if (keys.length === 1) {
318
+ const inner = select(node.states[keys[0]!]!, (value as Record<string, unknown>)[keys[0]!], snapshot, event, read);
319
+ return inner?.length ? inner : next(node, snapshot, event, read);
320
+ }
321
+ const all: Transition[] = [];
322
+ for (const key of keys) {
323
+ const sub = (value as Record<string, unknown>)[key];
324
+ if (!sub) continue;
325
+ all.push(...(select(node.states[key]!, sub, snapshot, event, read) ?? []));
326
+ }
327
+ return all.length ? all : next(node, snapshot, event, read);
328
+ }
329
+
330
+ /** The writings xstate would select for `type` from `sender`, every payload-reading guard passing. */
331
+ export function selectPayloadBlind(
332
+ snapshot: AnyMachineSnapshot,
333
+ type: string,
334
+ sender: Sender | undefined,
335
+ ): Transition[] {
336
+ const read = { payload: false };
337
+ const base: Record<string, unknown> = { type, ...(sender ? { [SENDER_KEY]: sender } : {}) };
338
+ const watch = (prop: string | symbol): void => {
339
+ if (typeof prop === "string" && prop !== "type" && prop !== SENDER_KEY) read.payload = true;
340
+ };
341
+ const probe = new Proxy(base, {
342
+ get(target, prop, receiver) {
343
+ watch(prop);
344
+ return Reflect.get(target, prop, receiver);
345
+ },
346
+ has(target, prop) {
347
+ watch(prop);
348
+ return Reflect.has(target, prop);
349
+ },
350
+ }) as AnyEventObject;
351
+ const machine = snapshot.machine as AnyStateMachine;
352
+ return select(machine.root, snapshot.value, snapshot, probe, read) ?? [];
353
+ }
354
+
355
+ /** xstate's `can()`: a selected writing that neither targets nor acts is not a move. */
356
+ const moves = (ts: Transition[]): boolean => ts.some((t) => t.target !== undefined || t.actions.length > 0);
357
+
358
+ /** What a registration needs to ask the party questions: who it sends as, its seat, its Machine. */
359
+ export type PartySeat = { sender?: Sender; invokeId?: string; invoker?: AnyActorRef };
360
+
361
+ function seated(reg: PartySeat): { machine: AnyStateMachine; seat: Node; snapshot: AnyMachineSnapshot } | undefined {
362
+ if (!reg.sender || !reg.invokeId || !reg.invoker) return undefined;
363
+ const snapshot = reg.invoker.getSnapshot() as AnyMachineSnapshot | undefined;
364
+ const machine = snapshot?.machine as AnyStateMachine | undefined;
365
+ if (!snapshot || !machine?.root) return undefined;
366
+ const seat = seatOf(snapshot, reg.invokeId);
367
+ return seat ? { machine, seat, snapshot } : undefined;
368
+ }
369
+
370
+ /**
371
+ * Would the run take `type` from this registration's sender NOW, payload-blind (ADR-0029)? The one
372
+ * question behind an Agent's Allowed picks and a Gate's `acceptsNow`: xstate's selection for an
373
+ * event that carries the sender, a payload-reading guard counted as passing, must move something,
374
+ * and every writing it selects must be one the sender may take. Undefined when the question cannot
375
+ * be asked (no seat); the caller then asks `mayMove`. Fails open like ADR-0029's readings: a wrong
376
+ * `true` costs one refused send, and delivery still judges it exactly.
377
+ */
378
+ export function takesNow(reg: PartySeat, type: string): boolean | undefined {
379
+ const at = seated(reg);
380
+ if (!at) return undefined;
381
+ try {
382
+ const selected = selectPayloadBlind(at.snapshot, type, reg.sender);
383
+ if (!moves(selected)) return false;
384
+ return selected.every((t) => mayTake(at.machine, reg.sender!, at.seat, t.source, type));
385
+ } catch {
386
+ return true;
387
+ }
388
+ }
389
+
390
+ /** A writing a delivery would run that its sender may not take: its state, and who owns it. */
391
+ export type NotTakenWriting = { state: string; owners: Sender[] };
392
+
393
+ /**
394
+ * At delivery, exact: the writings xstate WILL select for this event (its real payload, its real
395
+ * sender) that the sender may not take. Empty: deliver. Not empty: refuse before the Machine sees it.
396
+ */
397
+ export function notTaken(reg: PartySeat, event: AnyEventObject): NotTakenWriting[] {
398
+ const at = seated(reg);
399
+ if (!at) return [];
400
+ try {
401
+ return at.machine
402
+ .getTransitionData(at.snapshot, event)
403
+ .filter((t) => !mayTake(at.machine, reg.sender!, at.seat, t.source, event.type))
404
+ .map((t) => {
405
+ const owner = ownerOf(t.source, at.machine);
406
+ return { state: t.source.id, owners: owner ? partiesAt(owner, at.machine) : [] };
407
+ });
408
+ } catch {
409
+ return [];
410
+ }
411
+ }
412
+
413
+ // --- The load walk's warnings over parties and writings (ADR-0015) --------------------------------
414
+
415
+ /** Neither above the other, and their nearest common state is parallel: two regions. */
416
+ function orthogonal(a: Node, b: Node): boolean {
417
+ if (isAncestorOrSelf(a, b) || isAncestorOrSelf(b, a)) return false;
418
+ for (let n: Node | undefined = a.parent; n; n = n.parent) if (isAncestorOrSelf(n, b)) return n.type === "parallel";
419
+ return false;
420
+ }
421
+
422
+ /** Can the two states be active at the same time? */
423
+ const together = (a: Node, b: Node): boolean => isAncestorOrSelf(a, b) || isAncestorOrSelf(b, a) || orthogonal(a, b);
424
+
425
+ /** One writing: one `on` key on one state, with its candidates (an array writing has several). */
426
+ type Writing = { node: Node; name: string; candidates: Transition[] };
427
+
428
+ function writingsOf(machine: AnyStateMachine): Writing[] {
429
+ const defs = vocabularyOf(machine);
430
+ const out: Writing[] = [];
431
+ for (const node of allNodes(machine))
432
+ for (const [name, candidates] of node.transitions)
433
+ if (defs?.has(name) ?? (!name.includes(".") && name !== "*"))
434
+ out.push({ node, name, candidates: candidates as Transition[] });
435
+ return out;
436
+ }
437
+
438
+ const targetsOf = (w: Writing): string =>
439
+ [...new Set(w.candidates.flatMap((t) => (t.target ?? []).map((target) => target.id)))].sort().join(", ");
440
+
441
+ /** The line the warning shows: the deeper writing, declining every sender but its own party. */
442
+ function correctedLine(w: Writing, id: string): string {
443
+ const guard = `sentBy("${id}")`;
444
+ const only = w.candidates.length === 1 ? w.candidates[0]! : undefined;
445
+ const target = only?.target?.length === 1 ? `, target: "#${only.target[0]!.id}"` : ", …";
446
+ const body =
447
+ only === undefined
448
+ ? `[{ guard: ${guard}, … }, …] (on each candidate)`
449
+ : only.guard === undefined
450
+ ? `{ guard: ${guard}${target} }`
451
+ : `{ guard: and([${guard}, <its guard>])${target} }`;
452
+ return ` ${w.node.key}: { on: { ${w.name}: ${body} } }`;
453
+ }
454
+
455
+ /**
456
+ * The load walk's party warnings (ADR-0015). Warnings, never refusals: the Machine is legal, and
457
+ * delivery refuses what would misroute.
458
+ *
459
+ * - **Two parties' writings of one name that can be active together.** A seat — one party on its
460
+ * state — may take a writing of a name, and ANOTHER party's writing of that name can be active
461
+ * with the seat's state and is selected first: deeper on the same path, or in another parallel
462
+ * region. Unless that writing declines the seat's sender with `sentBy`, xstate selects it for
463
+ * the seat's event, delivery refuses the event, and the seat cannot send the name while it is
464
+ * active. The warning names both writings and shows the corrected line; the guard goes on the
465
+ * DEEPER writing, because xstate selects deepest first. A root writing is both parties', so the
466
+ * root-writing shape (a root `cancel`, a Gate below, the Agent's `cancel` in a mode) is the same
467
+ * case. Not when the seat is an Agent and the deeper writing's party is seated below the Agent's
468
+ * state: that state is busy while the party runs, so every Agent pick is refused there (ADR-0024).
469
+ * - **One def, one meaning.** One party may take two writings of one name on one path with
470
+ * different targets: its sender reads one description for both.
471
+ *
472
+ * `derivedGateIds` are the Gate invoke ids the walk named: the line says how to name one instead.
473
+ */
474
+ export function partyWarnings(machine: AnyStateMachine, derivedGateIds: ReadonlySet<string> = new Set()): string[] {
475
+ const writings = writingsOf(machine);
476
+ const seats = allNodes(machine).flatMap((node) => partiesAt(node, machine).map((sender) => ({ node, sender })));
477
+ const warnings = new Map<string, string>();
478
+ for (const seat of seats) {
479
+ const reader =
480
+ seat.sender.party === "agent" ? `Agent "${seat.sender.id}"` : `the caller of Gate "${seat.sender.id}"`;
481
+ for (const own of writings) {
482
+ if (!mayTake(machine, seat.sender, seat.node, own.node, own.name)) continue;
483
+ for (const other of writings) {
484
+ if (other.name !== own.name || other === own) continue;
485
+ if (mayTake(machine, seat.sender, seat.node, other.node, other.name)) {
486
+ // One def, one meaning: the same party, one path, different targets.
487
+ if (!isAncestorOrSelf(own.node, other.node) || targetsOf(own) === targetsOf(other)) continue;
488
+ warnings.set(
489
+ `meaning:${seat.sender.party}:${seat.sender.id}:${own.name}:${own.node.id}:${other.node.id}`,
490
+ `"${own.name}" is written for ${senderText(seat.sender)} on "${own.node.id}" (→ ${targetsOf(own) || "no target"}) ` +
491
+ `and on "${other.node.id}" (→ ${targetsOf(other) || "no target"}): ${reader} reads one description for both ` +
492
+ `— make the def's description true at each writing, or give them distinct names`,
493
+ );
494
+ continue;
495
+ }
496
+ const deeper = isAncestorOrSelf(own.node, other.node) && own.node !== other.node;
497
+ const parallel = orthogonal(own.node, other.node);
498
+ if ((!deeper && !parallel) || !together(seat.node, other.node)) continue;
499
+ if (other.candidates.every((t) => declines((t as { guard?: unknown }).guard, seat.sender.id))) continue;
500
+ const ownerNode = ownerOf(other.node, machine);
501
+ // Under the Rest rule (ADR-0024) a party seated BELOW the Agent's state keeps that state
502
+ // busy while it is active, so every pick of the Agent is refused there, whatever its guard:
503
+ // the Agent never sends this name while the deeper writing is active, and no `sentBy` on it
504
+ // would change that. Nothing to warn about.
505
+ if (
506
+ seat.sender.party === "agent" &&
507
+ ownerNode !== undefined &&
508
+ ownerNode !== seat.node &&
509
+ isAncestorOrSelf(seat.node, ownerNode)
510
+ )
511
+ continue;
512
+ const owners = ownerNode ? partiesAt(ownerNode, machine) : [];
513
+ const owner = owners.find((p) => p.id !== seat.sender.id) ?? owners[0];
514
+ const named = owner?.party === "gate" && derivedGateIds.has(owner.id);
515
+ warnings.set(
516
+ `party:${seat.sender.party}:${seat.sender.id}:${other.name}:${other.node.id}`,
517
+ `"${own.name}" on "${own.node.id}" (${senderText(seat.sender)}) and on "${other.node.id}" ` +
518
+ `(${owners.map(senderText).join(" and ")}) can be active together${parallel ? " (parallel regions)" : ""}; ` +
519
+ `${senderText(seat.sender)}'s "${own.name}" would run ${owner ? `${senderText(owner)}'s` : "the other"} writing` +
520
+ `${parallel ? " too" : ""}, so jr2 refuses it while both are active. Make the ` +
521
+ `${parallel ? `writing on "${other.node.id}"` : "deeper writing"} decline other senders:\n` +
522
+ correctedLine(other, owner?.id ?? "<its party>") +
523
+ (named
524
+ ? `\n(the Gate's invoke id "${owner!.id}" is derived from its state; give its invoke an \`id\` to name it)`
525
+ : ""),
526
+ );
527
+ }
528
+ }
529
+ }
530
+ return [...warnings.values()];
531
+ }
532
+
533
+ /** How a sender reads in a message: `Agent "coder"`, `Gate "review"`. */
534
+ export const senderText = (s: Sender): string => (s.party === "agent" ? `Agent "${s.id}"` : `Gate "${s.id}"`);