@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
@@ -28,9 +28,11 @@ import { getStateNodes } from "xstate";
28
28
  import type { ActorSystem, AnyActorRef, AnyEventObject, AnyMachineSnapshot, AnyStateMachine } from "xstate";
29
29
  import { SENDER_KEY, type EventDef, type Sender } from "@jr2/agent-protocol";
30
30
  import type { AgentAdmission } from "./actor.ts";
31
+ import type { CallerRecord } from "./callers.ts";
32
+ import type { AnyHandler, GateSlotParts } from "./gate.ts";
31
33
  import type { EffectMarker, EffectRow } from "./effect.ts";
32
34
  import type { EffectorPlacement, EffectorTarget } from "./http-effect.ts";
33
- import type { NoticeScope, TakenNotices } from "./notices.ts";
35
+ import type { LedgerBody, NoticeScope, RaiseOptions, TakenNotices } from "./notices.ts";
34
36
  import type { AnswerSeat } from "./pick.ts";
35
37
  import type { Notice, PickAnswer } from "./wire.ts";
36
38
  import type { SandboxPort } from "./workspace.ts";
@@ -82,6 +84,20 @@ export type Registration = {
82
84
  * Harness runs on the host, in no Sandbox at all): those are the Instance token's business.
83
85
  */
84
86
  sandbox?: string;
87
+ /**
88
+ * An Agent's: its SLOT (ADR-0076) — the actor path of its Machine and its agent name, which is its
89
+ * continued conversation's Instance ID without the run id ({@link continuedIid}). The Harness
90
+ * states it on every control request, from its own record of the conversation, and the
91
+ * Orchestrator speaks here only for that slot. Two Agents with one key in two child Machines are
92
+ * two slots. Absent on gates.
93
+ */
94
+ slot?: string;
95
+ /** An Agent's: the step boundary's read of its conversation's pending notices (ADR-0062), each
96
+ * reserved for this Turn until {@link noticesHeard} or the Turn's end releases it. */
97
+ boundaryNotices?: () => Promise<Array<{ id: string; notice: LedgerBody }>>;
98
+ /** An Agent's: the Harness appended these step-boundary notices — they are heard, ledgered in one
99
+ * save. Answers how many were this Turn's reservations. */
100
+ noticesHeard?: (ids: readonly string[]) => number;
85
101
  /** Close over the invoking state's `sendBack`; delivery lands where the actor was invoked. */
86
102
  deliver: (event: DeliveredEvent) => void;
87
103
  /** An Agent's: the Harness read this surface (`GET /agents/:iid/surface`), so it has the Turn in
@@ -110,6 +126,57 @@ export type Registration = {
110
126
  * answer has nowhere to go.
111
127
  */
112
128
  conversation?: string;
129
+ /**
130
+ * A Gate slot's (ADR-0011): its handlers by Hook kind id and event key, its `reply`, and the names
131
+ * of its `delivers`. What the Hook's door reads to fan a delivery out to this Gate. Absent on a
132
+ * plain Gate and on an Agent.
133
+ */
134
+ hooked?: {
135
+ hooks: Readonly<Record<string, Readonly<Record<string, AnyHandler>>>>;
136
+ reply?: GateSlotParts["reply"];
137
+ delivers: readonly string[];
138
+ };
139
+ /**
140
+ * A Pool's `source` Gate (ADR-0017): the push half of its Source. `hears` says whether a handler
141
+ * of the Source hears a Hook kind and event key; `hear` runs that handler and answers the
142
+ * Source's verdict. What the Hook's door reads to bring a delivery to the Pool. Absent on every
143
+ * other Gate and on an Agent.
144
+ */
145
+ source?: SourceSeat;
146
+ };
147
+
148
+ /** One Hook delivery as a Source's handler hears it (ADR-0017): the Hook, its kind and event key,
149
+ * the delivery id, the parsed payload and the Hook as a Caller. */
150
+ export type SourceDelivery = {
151
+ hook: string;
152
+ kind: string;
153
+ key: string;
154
+ delivery: string;
155
+ payload: unknown;
156
+ caller: CallerRecord;
157
+ };
158
+
159
+ /** The verdicts of a Source on one delivery (ADR-0017). */
160
+ export const SOURCE_VERDICTS = ["held", "existing", "hold-full", "reread", "skipped", "paused"] as const;
161
+ export type SourceVerdictName = (typeof SOURCE_VERDICTS)[number];
162
+
163
+ /** A Source's verdict on one delivery (ADR-0017): `key` for a carried item, `run` when a run holds
164
+ * that key, `reason` for a skip. */
165
+ export type SourceVerdictLine = { verdict: SourceVerdictName; key?: string; run?: string; reason?: string };
166
+
167
+ /** A Source's verdict as its seat gives it: the line, and the carried item, which only the
168
+ * Source's `reply` reads (ADR-0011). The item never goes in the outcome. */
169
+ export type SourceHeard = SourceVerdictLine & { item?: unknown };
170
+
171
+ /** The push half of a Pool's Source, on its `source` Gate's registration (ADR-0017). */
172
+ export type SourceSeat = {
173
+ hears(kind: string, key: string): boolean;
174
+ /** Run the handler, check the key and hold the item. The answer is the verdict; a `held` item is
175
+ * in the Pool's context when it resolves, and the caller saves the run before it answers. */
176
+ hear(d: SourceDelivery): Promise<SourceHeard>;
177
+ /** The Source's `reply`, when it has one: the text that the person who acted reads, from the
178
+ * verdict (ADR-0011). Pure; a throw is the caller's to log. */
179
+ reply?(verdict: SourceHeard, caller: CallerRecord): unknown;
113
180
  };
114
181
 
115
182
  /**
@@ -266,6 +333,9 @@ export class OutstandingError extends Error {
266
333
  }
267
334
  }
268
335
 
336
+ /** The Caller that a Gate's event carries on its {@link Sender} (ADR-0011, ADR-0078). */
337
+ export type SenderCaller = Pick<Sender, "via" | "name" | "onBehalfOf" | "checked">;
338
+
269
339
  /** Refuse a caller's body that carries the sender (ADR-0013, ADR-0015): only the Orchestrator writes
270
340
  * it, from the registration, so neither the Agent's wire nor the gates route takes it. */
271
341
  export function refuseSenderField(payload: unknown): void {
@@ -313,9 +383,47 @@ export function actorPath(ref: AnyActorRef): string[] {
313
383
  * Turn adds a random suffix, a burned conversation's successor its epoch), and the Agent's Actor
314
384
  * names the conversation it burns with it.
315
385
  */
386
+ /** An Agent's slot (ADR-0076): its continued conversation's Instance ID without the run id —
387
+ * `<machine actor path>/<agent>`. What the Harness states and the registration records. Each
388
+ * segment of the path is encoded ({@link slotSegment}) before the segments are joined with `.`,
389
+ * so two paths never have one spelling (ADR-0057). The root Machine's path is `root`, a spelling
390
+ * that no encoded path has: a segment `root` and the empty segment are escaped. */
391
+ export function agentSlot(machine: AnyActorRef | undefined, agentName: string): string {
392
+ return `${slotPath(machine)}/${agentName}`;
393
+ }
394
+
395
+ /** The Machine part of a slot ({@link agentSlot}): the encoded actor path of the Machine instance,
396
+ * `root` for the run's root. A Steering message held for a slot ends with this Machine (ADR-0002). */
397
+ export function slotPath(machine: AnyActorRef | undefined): string {
398
+ return (machine ? actorPath(machine).map(slotSegment).join(".") : "") || "root";
399
+ }
400
+
401
+ /**
402
+ * One actor id as a segment of a slot's path (ADR-0057). A letter, a digit, `-` and `_` stay as
403
+ * they are; each other character becomes `~` and two upper-case hex digits for each of its UTF-8
404
+ * bytes. So a segment has no `.` (the joint), no `/` (the iid's joint) and nothing that a URL or
405
+ * a header must escape, and the encoding is one to one: `~` occurs only as an escape. A Pool's
406
+ * worker has its item's id as its actor id, and an item id can be any text. Two segments are
407
+ * escaped although each character in them may stay: `root` is `~72oot` and the empty id is `~`
408
+ * (a `~` that no hex follows), so no encoded path spells `root`, the root Machine's path, or is
409
+ * empty, which the root's path also was.
410
+ */
411
+ export function slotSegment(id: string): string {
412
+ if (id === "") return "~";
413
+ if (id === "root") return "~72oot";
414
+ let out = "";
415
+ for (const ch of id) {
416
+ if (/^[A-Za-z0-9_-]$/.test(ch)) {
417
+ out += ch;
418
+ continue;
419
+ }
420
+ for (const byte of new TextEncoder().encode(ch)) out += `~${byte.toString(16).toUpperCase().padStart(2, "0")}`;
421
+ }
422
+ return out;
423
+ }
424
+
316
425
  export function continuedIid(machine: AnyActorRef | undefined, runId: string, agentName: string): string {
317
- const path = (machine ? actorPath(machine).join(".") : "") || "root";
318
- return `${runId}/${path}/${agentName}`;
426
+ return `${runId}/${agentSlot(machine, agentName)}`;
319
427
  }
320
428
 
321
429
  /** The address of a run's gate: gate ids are run-scoped by construction (ADR-0011). */
@@ -328,11 +436,23 @@ export function agentAddress(instanceId: string): string {
328
436
  return `agent/${instanceId}`;
329
437
  }
330
438
 
439
+ /** What the run host hears of the table: a surface that opens, and one that closes. A Gate's
440
+ * `openedAt` is recorded here (ADR-0011). */
441
+ export type RegistrationWatch = {
442
+ opened?: (reg: Registration) => void;
443
+ closed?: (reg: Registration) => void;
444
+ };
445
+
331
446
  export class RegistrationTable {
332
447
  private readonly byAddress = new Map<string, Registration>();
333
448
  /** The same registrations by run, so a run's are read without a scan of every run's: the run host
334
449
  * reads them on every microstep of the run (`cause.ts`). */
335
450
  private readonly ofRun = new Map<string, Set<Registration>>();
451
+ private readonly watch: RegistrationWatch;
452
+
453
+ constructor(watch: RegistrationWatch = {}) {
454
+ this.watch = watch;
455
+ }
336
456
 
337
457
  /** Register a live surface; returns its disposer (actors call it from their stop cleanup). */
338
458
  register(reg: Registration): () => void {
@@ -345,6 +465,8 @@ export class RegistrationTable {
345
465
  const ofRun = this.ofRun.get(reg.runId) ?? new Set<Registration>();
346
466
  ofRun.add(reg);
347
467
  this.ofRun.set(reg.runId, ofRun);
468
+ this.watch.opened?.(reg);
469
+ let disposed = false;
348
470
  return () => {
349
471
  // Dispose only our own entry — a re-registration under the same address must not be
350
472
  // clobbered by a stale disposer running late (dev reload, restore races).
@@ -352,6 +474,8 @@ export class RegistrationTable {
352
474
  const runRegs = this.ofRun.get(reg.runId);
353
475
  runRegs?.delete(reg);
354
476
  if (runRegs?.size === 0) this.ofRun.delete(reg.runId);
477
+ if (!disposed) this.watch.closed?.(reg);
478
+ disposed = true;
355
479
  };
356
480
  }
357
481
 
@@ -371,9 +495,10 @@ export class RegistrationTable {
371
495
  * to their wire (404 / 400, MCP tool errors). The payload is parsed by the named def's
372
496
  * schema, so what lands in the Machine is exactly the validated shape — plus the registration's
373
497
  * sender, written here and nowhere else (ADR-0015). A delivery that would run another party's
374
- * writing throws {@link NotTakenError}.
498
+ * writing throws {@link NotTakenError}. `caller` is the Caller that answers a Gate (ADR-0011,
499
+ * ADR-0078): a Gate's Sender carries it, and an Agent's never does.
375
500
  */
376
- deliver(address: string, type: string, payload: unknown): void {
501
+ deliver(address: string, type: string, payload: unknown, caller?: SenderCaller): void {
377
502
  const reg = this.byAddress.get(address);
378
503
  if (!reg) throw new UnknownAddressError(`no live registration at "${address}"`);
379
504
  refuseSenderField(payload);
@@ -388,10 +513,11 @@ export class RegistrationTable {
388
513
  throw new EventValidationError(`invalid "${type}" payload: ${parsed.error.message}`);
389
514
  }
390
515
  // `type` after the payload: a payload never names the event the Machine receives (ADR-0011).
516
+ const sender = reg.sender && caller && reg.sender.party === "gate" ? { ...reg.sender, ...caller } : reg.sender;
391
517
  const event: DeliveredEvent = {
392
518
  ...parsed.data,
393
519
  type: def.name,
394
- ...(reg.sender ? { [SENDER_KEY]: reg.sender } : {}),
520
+ ...(sender ? { [SENDER_KEY]: sender } : {}),
395
521
  };
396
522
  // Exact, with the real payload (ADR-0015): a delivery whose selection holds a writing that is
397
523
  // not this sender's never reaches the Machine.
@@ -417,12 +543,34 @@ export class RegistrationTable {
417
543
  * and on the row. `instance` is set for a foreign caller, whose Orchestrator this one cannot
418
544
  * verify: opaque text, never resolved.
419
545
  */
420
- export type DispatchedBy = { run: string; slot: string; effect: string; depth: number; instance?: string };
546
+ export type DispatchedBy = {
547
+ run: string;
548
+ slot: string;
549
+ effect: string;
550
+ depth: number;
551
+ instance?: string;
552
+ /** The idempotency key when a Pool of runs started the run (ADR-0017): the item's id. The store
553
+ * scopes it to the callee Workflow, as it scopes `effect`. Absent: the key is `effect`. */
554
+ key?: string;
555
+ /** The Hook delivery that carried the item to the Pool (ADR-0017): the Hook's name, the delivery
556
+ * id, and the party that the signed payload names. Absent for an item that a read or `next` gave. */
557
+ hook?: { name: string; delivery: string; onBehalfOf?: string };
558
+ };
559
+
560
+ /** The idempotency key that a start carries (ADR-0017, ADR-0071): a Pool's `key`, else the caller's
561
+ * ledger row id. */
562
+ export const dispatchKeyOf = (by: Pick<DispatchedBy, "effect" | "key">): string => by.key ?? by.effect;
421
563
 
422
564
  /** The header a foreign `dispatch` carries its {@link DispatchedBy} in, as JSON (ADR-0071): out of
423
565
  * band, so the callee's door reserves no body field for it. */
424
566
  export const DISPATCHED_BY_HEADER = "jr2-dispatched-by";
425
567
 
568
+ /** The header that makes a foreign start a key-only ask (ADR-0071), with the value `true`: the door
569
+ * answers the run under the key of {@link DISPATCHED_BY_HEADER}, or `200 { existing: false }`, and
570
+ * starts nothing. The Instance Effector sends it, with no body, when the slot's schema refuses the
571
+ * body. */
572
+ export const KEY_ONLY_HEADER = "jr2-key-only";
573
+
426
574
  /**
427
575
  * A dispatch's start as the run host answers it in-process (ADR-0071): the run, and whether the key
428
576
  * found one that already existed; or the door's refusal, with the status `POST /workflows/:name/runs`
@@ -447,16 +595,24 @@ export type RunBinding = {
447
595
  /** The run's depth in a chain of dispatches (ADR-0071): 0 for a run nothing dispatched. Absent
448
596
  * on a bare unit-test binding, which reads 0. */
449
597
  depth?: number;
598
+ /** The first Caller of the run's chain of starts (ADR-0078): the Caller that started this run,
599
+ * or the run that dispatched it, and so on. A foreign `dispatch` sends it to the other door as
600
+ * `onBehalfOf` (ADR-0071). Undefined when jr2 started the chain. Absent on a bare binding. */
601
+ firstCaller?: () => Promise<{ via: string; name: string; shown?: string; onBehalfOf?: string } | undefined>;
450
602
  /**
451
603
  * The in-process half of a `dispatch` Effect (ADR-0071): the run host, reached directly, with no
452
604
  * wire and no token — the Orchestrator is the single writer of runs. `start` answers a refusal as
453
605
  * {@link DispatchStart}, never by throwing; `settled` resolves with the run's status once it is no
454
606
  * longer live — at once for a run already settled, and across a restore in either order — and
455
- * rejects when `signal` aborts. Absent outside a RunHost: a dispatch with no target faults.
607
+ * rejects when `signal` aborts. `lookup` is the key check alone: the run of `workflow` that
608
+ * carries the key `key`, in any status, while its row lives; the Effect asks it before it reads
609
+ * its input, so a run that exists is answered whatever the slot's schema says now. Absent
610
+ * outside a RunHost: a dispatch with no target faults.
456
611
  */
457
612
  dispatch?: {
458
613
  start(workflow: string, input: Record<string, unknown>, by: DispatchedBy): Promise<DispatchStart>;
459
614
  settled(runId: string, signal: AbortSignal): Promise<RunStatus>;
615
+ lookup?(workflow: string, key: string): Promise<{ runId: string } | undefined>;
460
616
  };
461
617
  /** The hash of the Assembly the run holds (ADR-0069): the one it started under, through
462
618
  * restarts. Absent for a run no Assembly started. */
@@ -488,9 +644,18 @@ export type RunBinding = {
488
644
  * Record an Agent invocation's durable admission in the host ledger (ADR-0016): persisted
489
645
  * beside the snapshot in the same RunBlob save, keyed by iid (globally unique, so the map is
490
646
  * flat). Optional so a bare unit-test binding can omit it — then admissions simply are not
491
- * durable.
647
+ * durable. `delivered` are the notices the admission carried, delivered by this save in `taker`'s
648
+ * conversation — the Instance ID the admission rides (ADR-0062). `member`: the Workspace the
649
+ * conversation was admitted in, its slot and whether it continues — so it is in the audience of
650
+ * every kill there from now on, in the same save.
492
651
  */
493
- recordAdmission?: (instanceId: string, admission: AgentAdmission, delivered?: readonly string[]) => void;
652
+ recordAdmission?: (
653
+ instanceId: string,
654
+ admission: AgentAdmission,
655
+ delivered?: readonly string[],
656
+ taker?: string,
657
+ member?: { workspace: string; slot: string; continued: boolean },
658
+ ) => void;
494
659
  /**
495
660
  * The admission ledger's READ half (ADR-0016): the last admission ledgered under this iid. A
496
661
  * continued Turn asks it whether its conversation was ever admitted — only then can the Harness
@@ -521,14 +686,31 @@ export type RunBinding = {
521
686
  * `older` names earlier events this one tells the same as, marked raised with it (`NoticeLedger`).
522
687
  * A burned conversation's notice is raised by `bumpEpoch` itself, in the same save.
523
688
  */
524
- raiseNotice?: (notice: Notice, to: string, source?: string, older?: readonly string[]) => void;
689
+ raiseNotice?: (notice: Notice, to: string, source?: string, older?: readonly string[], opts?: RaiseOptions) => void;
525
690
  /** Put back a notice an admission carried that no model heard, WITH its source (`NoticeLedger.restore`):
526
- * a raise would drop it, its source being seen. Persisted at the write. */
527
- restoreNotice?: (notice: Notice, to: string, source?: string) => void;
528
- /** Reserve the pending notices in one admission's scope. They are delivered by the
529
- * `recordAdmission` that ledgers it (its `delivered` ids), or released if the admission fails. */
691
+ * a raise would drop it, its source being seen. A Workspace notice is pending again for `taker`,
692
+ * the conversation that did not hear it. Persisted at the write. */
693
+ restoreNotice?: (notice: LedgerBody, to: string, source?: string, taker?: string) => void;
694
+ /** Reserve the pending notices in one admission's scope, for its conversation (`scope.taker`).
695
+ * They are delivered by the `recordAdmission` that ledgers it (its `delivered` ids and `taker`),
696
+ * or released for that conversation if the admission fails. */
530
697
  takeNotices?: (scope: NoticeScope) => TakenNotices;
531
- releaseNotices?: (ids: readonly string[]) => void;
698
+ releaseNotices?: (ids: readonly string[], taker?: string) => void;
699
+ /** What `takeNotices` would reserve in this scope, reserving nothing (`NoticeLedger.peek`). */
700
+ peekNotices?: (scope: NoticeScope) => Array<{ id: string; notice: LedgerBody; source?: string }>;
701
+ /** The Machine's `steer` action (ADR-0002): hold a Steering message for the Agent `agentName` of
702
+ * `machine`, with the Sender whose words it relays. Throws when `machine` carries no Agent of
703
+ * that name. Absent outside a RunHost: the action then does nothing. */
704
+ steer?: (machine: AnyActorRef, agentName: string, text: string, from?: Sender) => void;
705
+ /** The Harness acknowledged a step boundary's notices (ADR-0062): each that `taker` reserved is
706
+ * heard there, persisted at the write. Answers how many were. */
707
+ acknowledgeNotices?: (ids: readonly string[], taker: string) => number;
708
+ /** The conversations each kill's `bash` answer told (ADR-0062, D4 rule 3), off the pod's list:
709
+ * they leave that kill's audience. Persisted at the write when anything changed. */
710
+ noticesTold?: (told: ReadonlyArray<{ source: string; takers: readonly string[] }>) => void;
711
+ /** A Turn of these conversations in `workspace` ended: a fresh one leaves every audience there,
712
+ * a continued one stays for its next Turn (ADR-0062). Persisted at the write. */
713
+ noticeTurnEnded?: (workspace: string, takers: readonly string[]) => void;
532
714
  /**
533
715
  * The pick the Turn now at this address has outstanding, or answered and not yet read (ADR-0024):
534
716
  * what the Agent's Actor asks when its Harness restarted under a Turn — a Turn whose pick is still
@@ -685,7 +867,57 @@ export type ChildErrorMarker = { kind: "child-error"; agent: string; pick: strin
685
867
 
686
868
  /** Every marker a run's mechanism puts on its per-run feed — an Effect's lines among them
687
869
  * (ADR-0067): its start, its stream, its output, its abort. */
688
- export type RunMarker = TurnMarker | PlacingMarker | ChildErrorMarker | EffectMarker;
870
+ export type RunMarker =
871
+ | TurnMarker
872
+ | PlacingMarker
873
+ | ChildErrorMarker
874
+ | EffectMarker
875
+ | GateMarker
876
+ | HookRefusedMarker
877
+ | PoolMarker;
878
+
879
+ /**
880
+ * What a Pool of runs did with one item (ADR-0017): `started` (its run, and `existing` when the key
881
+ * found a run that was there before), `settled` (the run ended, with its status, or the start
882
+ * ended with no run: `start-refused` with the door's report, `dispatch-error` with the error), and
883
+ * `carry-refused` (a held item that the Source's `item` schema does not parse), and `skipped` (an
884
+ * item of a read whose `itemId` threw or gave no id: it has no `key`, and `reason` says which item
885
+ * and why; one mark per item in a run's life). `pool` is the Pool's Machine id. Instance-token class
886
+ * like the Turn markers: a key is the workflow's data.
887
+ */
888
+ export type PoolMarker = {
889
+ kind: "pool";
890
+ pool: string;
891
+ mark: "started" | "settled" | "carry-refused" | "skipped";
892
+ /** The item's id: absent on `skipped`, whose item has none. */
893
+ key?: string;
894
+ run?: string;
895
+ existing?: boolean;
896
+ status?: string;
897
+ reason?: string;
898
+ };
899
+
900
+ /**
901
+ * A Gate's look gave its event, and the run took it (ADR-0011): the Gate's id and the event's name.
902
+ * The event's Sender is the Gate, and no Caller answered it. Instance-token class like the Turn
903
+ * markers: a Gate id can be one that the workflow computed from its data.
904
+ */
905
+ export type GateMarker = { kind: "looked"; gate: string; event: string };
906
+
907
+ /**
908
+ * A Hook's delivery reached an open Gate of the run, and the Gate did not take it (ADR-0011): its
909
+ * handler threw, gave an event that is not in its `delivers`, or gave one that the run does not take
910
+ * now. The run goes on. The Hook's name, the delivery id, the Gate, the event when there was one,
911
+ * and why. Instance-token class like the Turn markers: the reason can be a handler's own words.
912
+ */
913
+ export type HookRefusedMarker = {
914
+ kind: "hook-refused";
915
+ hook: string;
916
+ delivery: string;
917
+ gate: string;
918
+ event?: string;
919
+ reason: string;
920
+ };
689
921
 
690
922
  const bindings = new WeakMap<AnyActorSystem, RunBinding>();
691
923
 
@@ -18,7 +18,8 @@ export type RepoIdentity = {
18
18
  * absolute path for a local repository. Path case is KEPT: hosts are case-insensitive, paths on
19
19
  * most forges are not. */
20
20
  identity: string;
21
- /** `<slug>-<sha256(identity)[:8]>` — a DNS-1123 label of at most 49 characters. */
21
+ /** `<slug>-<sha256(identity)[:32]>` — a DNS-1123 label of at most 49 characters: a slug of at
22
+ * most 16 characters and 128 bits of the hash, so no one finds two identities with one key. */
22
23
  key: string;
23
24
  scheme: "https" | "http" | "ssh" | "git" | "local";
24
25
  };
@@ -139,7 +140,10 @@ function keyOf(identity: string): string {
139
140
  .toLowerCase()
140
141
  .replace(/[^a-z0-9]+/g, "-")
141
142
  .replace(/^-+|-+$/g, "")
142
- .slice(0, 40)
143
+ .slice(0, 16)
143
144
  .replace(/-+$/, "") || "repo";
144
- return `${slug}-${createHash("sha256").update(identity).digest("hex").slice(0, 8)}`;
145
+ // 128 bits (ADR-0051): the key names the cache that a Sandbox mounts, and a per-run url is the
146
+ // Agent's text. With 32 bits a search found a url of another repository with the same key in
147
+ // seconds, and that url mounted the other repository's cache.
148
+ return `${slug}-${createHash("sha256").update(identity).digest("hex").slice(0, 32)}`;
145
149
  }
package/src/repos.ts CHANGED
@@ -192,10 +192,28 @@ export function kubeRepos(opts: KubeReposOptions): RepoResources {
192
192
  return item;
193
193
  };
194
194
 
195
+ /** The resource that stands under `repo.key`, when it is `repo`'s identity; else a throw that names
196
+ * both urls (ADR-0051). The key is collision-resistant, so another identity under one key is a
197
+ * defect or an attack, and no statement of this port writes a resource of another identity: not
198
+ * its url, not its `secretRef`, not its clock. */
199
+ const sameIdentity = async (repo: { url: string; identity: string; key: string }): Promise<RepoItem> => {
200
+ const item = await get(repo.key);
201
+ const stands = item.metadata?.annotations?.[ANNOTATION_REPO_IDENTITY];
202
+ if (stands !== repo.identity) {
203
+ throw new Error(
204
+ `Repo "${repo.key}" stands for ${stands === undefined ? "no stated identity" : `"${stands}"`} ` +
205
+ `(url ${item.spec?.url ?? "unknown"}), not for "${repo.identity}" (url ${repo.url}): one key, two ` +
206
+ "repositories, so jr2 changes nothing on it (ADR-0051). Delete that Repo if it is stale.",
207
+ );
208
+ }
209
+ return item;
210
+ };
211
+
195
212
  return {
196
213
  async bind(repo) {
197
214
  const { secretRef, cr } = await resourceFor({ ...repo, bound: true });
198
215
  if (await create(cr)) return;
216
+ await sameIdentity(repo);
199
217
  // Already there from an earlier deploy: ONE merge patch says what the Machine resolves now —
200
218
  // url, credential, and the label `jr2 gc` honors. `secretRef: null` clears a credential the
201
219
  // config no longer names, so a dropped entry does not linger. The clock is not touched: it
@@ -216,12 +234,12 @@ export function kubeRepos(opts: KubeReposOptions): RepoResources {
216
234
  // Already there — the boot's statement, or an earlier run's. The eviction clock moves: the
217
235
  // run attached it now. The url stays as stated, whatever spelling this run brought.
218
236
  const clock = { metadata: { annotations: { [ANNOTATION_REPO_LAST_ATTACHED]: attached } } };
237
+ const standing = await sameIdentity(repo);
219
238
  // A Machine's slot binds it: the boot is its writer, and restates the credential at the next
220
- // deploy. Read nothing.
239
+ // deploy.
221
240
  if (repo.bound) return patch(repo.key, clock);
222
241
  // This run's slot is per-run, but another registered Machine may bind the identity — the
223
242
  // label says so, and then the boot is still the writer.
224
- const standing = await get(repo.key);
225
243
  if (standing.metadata?.labels?.[LABEL_REPO_BOUND] === "true") return patch(repo.key, clock);
226
244
  // Nothing binds it: no boot ever restates it, so this attach does — the credential resolved
227
245
  // against the url that STANDS, since the scheme picks the Secret's kind (config.ts) and the