@jr2/orchestrator 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- 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
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +5 -3
- package/src/actor.ts +541 -79
- package/src/agent.ts +114 -38
- package/src/ambient.ts +2 -1
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +169 -37
- package/src/charts.ts +34 -6
- package/src/config.ts +430 -178
- package/src/custodian.ts +74 -22
- package/src/customize.ts +188 -46
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect.ts +341 -44
- package/src/effector-wire.ts +160 -11
- package/src/fail.ts +209 -0
- package/src/harness-client.ts +541 -47
- package/src/http-effect.ts +495 -53
- package/src/http.ts +427 -16
- package/src/index.ts +60 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +84 -0
- package/src/machine-doc.ts +21 -3
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/notices.ts +66 -13
- package/src/open-body.ts +159 -23
- package/src/open-effect.ts +81 -8
- package/src/open.ts +59 -6
- package/src/parts.ts +184 -13
- package/src/party.ts +68 -2
- package/src/pick.ts +463 -11
- package/src/pool.ts +247 -36
- package/src/registration.ts +83 -4
- package/src/repos.ts +2 -1
- package/src/run-host.ts +565 -31
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +273 -38
- package/src/sandbox-kube.ts +3 -1
- package/src/secret-placement.ts +52 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +239 -56
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/vocabulary.ts +69 -14
- package/src/wire.ts +90 -16
- package/src/workspace.ts +697 -175
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The output of a run (ADR-0015). xstate v5 gives a Machine one output: the `output` at its root,
|
|
2
|
+
// beside `id` and `states`. The `output` of a final state directly below the root goes to that root
|
|
3
|
+
// `output` as `event.output`, and to nothing else. Thus a Machine that writes an `output` on such a
|
|
4
|
+
// final state and no root `output` ends with no output: its run status shows none, and the `onDone`
|
|
5
|
+
// of an invoke of it gets `event.output` as `undefined`. `types.output` does not stop this, so the
|
|
6
|
+
// load walk refuses the shape and the refusal gives the fix.
|
|
7
|
+
//
|
|
8
|
+
// A final state inside a compound state is legal: its `output` is the `event.output` of the `onDone`
|
|
9
|
+
// of that compound state. A Machine with no `output` anywhere is legal too: its result is in its
|
|
10
|
+
// `context`, and it says nothing false about an output.
|
|
11
|
+
|
|
12
|
+
import type { AnyStateMachine, StateNode } from "xstate";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Refuse a Machine whose root has no `output` while a final state directly below the root declares
|
|
16
|
+
* one (see the module comment). Thrown at build by `jr2Setup.createMachine` and at load by
|
|
17
|
+
* `partsOf`, so a Machine built with xstate's own `createMachine` is refused where `jr2 check`,
|
|
18
|
+
* `jr2 up` and the Orchestrator read it.
|
|
19
|
+
*/
|
|
20
|
+
export function refuseLostOutput(machine: AnyStateMachine): void {
|
|
21
|
+
const root = machine.root as StateNode<any, any>;
|
|
22
|
+
if (root.config.output !== undefined) return;
|
|
23
|
+
const lost = Object.values(root.states as Record<string, StateNode<any, any>>).filter(
|
|
24
|
+
(node) => node.type === "final" && node.config.output !== undefined,
|
|
25
|
+
);
|
|
26
|
+
if (!lost.length) return;
|
|
27
|
+
const names = lost.map((node) => `"${node.id}"`).join(", ");
|
|
28
|
+
const many = lost.length > 1;
|
|
29
|
+
const [states, declare, reach, write, keep, pass] = many
|
|
30
|
+
? ["final states", "declare", "these outputs reach", "one", "them on the final states", "them"]
|
|
31
|
+
: ["final state", "declares", "this output reaches", "it", "it on the final state", "it"];
|
|
32
|
+
throw new Error(
|
|
33
|
+
`machine "${machine.id}": ${states} ${names} ${declare} an output, and the root has no output — xstate v5 ` +
|
|
34
|
+
`gives a run only its root output, so ${reach} nothing and the run ends with none: write ${write} at the ` +
|
|
35
|
+
`root, as output: ({ context }) => ..., or keep ${keep} and pass ${pass} on with output: ({ event }) => ` +
|
|
36
|
+
`event.output at the root`,
|
|
37
|
+
);
|
|
38
|
+
}
|
package/src/sandbox-effect.ts
CHANGED
|
@@ -53,7 +53,7 @@ import type {
|
|
|
53
53
|
PushParts,
|
|
54
54
|
Unknown,
|
|
55
55
|
} from "./effect.ts";
|
|
56
|
-
import { lastPushOf, pushPairsOf } from "./effect.ts";
|
|
56
|
+
import { lastPushOf, needsASlot, pushPairsOf } from "./effect.ts";
|
|
57
57
|
import {
|
|
58
58
|
createEffectorClient,
|
|
59
59
|
EffectorForged,
|
|
@@ -126,12 +126,30 @@ function slotOf(kind: string, slot: string, named: string | undefined, ambient:
|
|
|
126
126
|
return named;
|
|
127
127
|
}
|
|
128
128
|
if (slots.length === 1) return slots[0]!;
|
|
129
|
+
const why = slots.length === 0 ? needsASlot(kind) : undefined;
|
|
130
|
+
if (why !== undefined)
|
|
131
|
+
throw new Error(`${kind} "${slot}" runs under a Workspace with no Repo Slot: ${why} (ADR-0051)`);
|
|
129
132
|
throw new Error(
|
|
130
133
|
`${kind} "${slot}" runs under a Workspace carrying ${slots.length === 0 ? "no" : "more than one"} Repo Slot ` +
|
|
131
134
|
`(${slots.join(", ") || "none"}) — say which: { slot: "<slot>" } (ADR-0051)`,
|
|
132
135
|
);
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
/** Where a file body's path is read from, relative to the work root: the Repo Slot's Worktree, or
|
|
139
|
+
* the work root itself (`.`) in a Workspace with no Repo Slot, whose steps share `/work` (ADR-0051).
|
|
140
|
+
* The Effector opens the file beneath it either way (ADR-0067). */
|
|
141
|
+
function fileRootOf(slot: string, named: string | undefined, ambient: AmbientHandles): string {
|
|
142
|
+
if (named === undefined && Object.keys(ambient.repos).length === 0) return ".";
|
|
143
|
+
return posix.relative(WORK_ROOT, ambient.repos[slotOf("http", slot, named, ambient)]!);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The branch of a Workspace with Repo Slots. A kind that reads it resolved a slot first, and a
|
|
147
|
+
* Workspace with a slot always has a branch, so its absence is a jr2 defect. */
|
|
148
|
+
function branchOf(ambient: AmbientHandles): string {
|
|
149
|
+
if (ambient.branch === undefined) throw new Error("a Workspace with a Repo Slot has no branch — a jr2 defect");
|
|
150
|
+
return ambient.branch;
|
|
151
|
+
}
|
|
152
|
+
|
|
135
153
|
/** The slot's repositories, relative to the work root: the Agent's common git dir is the pod-local
|
|
136
154
|
* clone's, beside the branch Worktree (ADR-0004); the cache is the Binding's key (ADR-0051). */
|
|
137
155
|
export function repoRefOf(ambient: AmbientHandles, repoSlot: string): RepoRef & { url: string } {
|
|
@@ -151,36 +169,159 @@ export function repoRefOf(ambient: AmbientHandles, repoSlot: string): RepoRef &
|
|
|
151
169
|
|
|
152
170
|
// --- The definitions -------------------------------------------------------------------------------
|
|
153
171
|
|
|
154
|
-
/**
|
|
172
|
+
/** The options of `push()`. */
|
|
155
173
|
export type PushOptions = {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*
|
|
174
|
+
// A pinned commit the Agent pruned is refused by the remote, never substituted (ADR-0067).
|
|
175
|
+
/**
|
|
176
|
+
* `true`: the input of the Effect names the commit to push (`{ sha }`, a full commit id, for
|
|
177
|
+
* example one that a Gate approved). Default: the tip of the Agent's branch when the Effect
|
|
178
|
+
* starts. When the Agent removed that commit from its branch, the remote refuses the push. jr2
|
|
179
|
+
* never pushes another commit in its place.
|
|
180
|
+
*
|
|
181
|
+
* When it starts, it refuses a `sha` that is not a full commit id: `push({ pin: true }): its
|
|
182
|
+
* input's sha is not a full commit id (got <sha>)`.
|
|
183
|
+
*/
|
|
159
184
|
pin?: boolean;
|
|
160
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* `true`: push signed copies of the commits. The `sign` field of the `git.credentials` entry that
|
|
187
|
+
* the Binding matches names the key. Default `false`.
|
|
188
|
+
*
|
|
189
|
+
* At `jr2 up`, it refuses an entry with no `sign` field: `<push> signed, and git.credentials
|
|
190
|
+
* entry "<match>" holds no sign field — add sign: { sshKey: "<a Secret with identity>", name:
|
|
191
|
+
* "<committer name>", email: "<committer email>" } to it; the email must belong to the forge
|
|
192
|
+
* account that holds the key (ADR-0051, ADR-0067)`. `<push>` names the push: `workflow
|
|
193
|
+
* "<workflow>": push "<slot>" pushes Repo Slot "<name>" (<url>)`.
|
|
194
|
+
*/
|
|
161
195
|
sign?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
*
|
|
164
|
-
* `
|
|
196
|
+
/**
|
|
197
|
+
* `true`: push one commit that holds all the changes. The input of the Effect gives its message
|
|
198
|
+
* (`{ message }`). Signed, its committer is the signer. Unsigned, it is the `committer` of the
|
|
199
|
+
* `git.credentials` entry that the Binding matches. Default `false`.
|
|
200
|
+
*
|
|
201
|
+
* When it starts, it refuses an input with no message: `push({ squash: true }): its input
|
|
202
|
+
* carries no message`.
|
|
203
|
+
*
|
|
204
|
+
* At `jr2 up`, it refuses an unsigned squash whose entry has no `sign` and no `committer`:
|
|
205
|
+
* `<push> squashed without sign, and git.credentials entry "<match>" holds neither sign nor
|
|
206
|
+
* committer — the squashed commit is new and needs a committer; add committer: { name:
|
|
207
|
+
* "<committer name>", email: "<committer email>" } to it, or sign the push — push({ squash:
|
|
208
|
+
* true, sign: true }) — and add sign: { sshKey: "<a Secret with identity>", name: "<committer
|
|
209
|
+
* name>", email: "<committer email>" } to it (ADR-0051, ADR-0067)`.
|
|
210
|
+
*/
|
|
165
211
|
squash?: boolean;
|
|
166
|
-
/**
|
|
212
|
+
/** `true`: replace what the remote branch holds. Default `false`. */
|
|
167
213
|
force?: boolean;
|
|
168
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* The Repo Slot to push. Write it only when the Workspace has more than one.
|
|
216
|
+
*
|
|
217
|
+
* When it starts, it refuses:
|
|
218
|
+
* - no `slot` under a Workspace with more than one Repo Slot: `push "<slot>" runs under a
|
|
219
|
+
* Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" }
|
|
220
|
+
* (ADR-0051)`
|
|
221
|
+
* - a `slot` that the Workspace does not have: `push "<slot>" names Repo Slot "<name>", which
|
|
222
|
+
* its Workspace does not carry (<slots>)`
|
|
223
|
+
* - a Workspace with no Repo Slot: `push "<slot>" runs under a Workspace with no Repo Slot: a
|
|
224
|
+
* push publishes a Repo Slot's branch, and there is none — declare one: repos: { app: "<url>" }
|
|
225
|
+
* (ADR-0051)`. `jr2 up` refuses it first, with the same reason.
|
|
226
|
+
*/
|
|
169
227
|
slot?: string;
|
|
170
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* How long jr2 waits for the push: a number and a unit, such as `"90s"` or `"5m"`. Default
|
|
230
|
+
* `"5m"`. With no answer in that time, the output is `unknown`. It refuses a value that is not a
|
|
231
|
+
* positive duration: `timeout "<timeout>" is not a positive duration (like "30s", "5m")`.
|
|
232
|
+
*/
|
|
171
233
|
timeout?: Duration;
|
|
172
234
|
};
|
|
173
235
|
|
|
174
|
-
/**
|
|
236
|
+
/** The input of a `push` Effect: `{ sha }` with `pin`, and `{ message }` with `squash`. */
|
|
175
237
|
export type PushInput<O extends PushOptions> = (O["pin"] extends true ? { sha: string } : unknown) &
|
|
176
238
|
(O["squash"] extends true ? { message: string } : unknown);
|
|
177
239
|
|
|
178
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* The sync's answer as a push's output carries it: a field beside the outcome, never the outcome.
|
|
242
|
+
*
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
179
245
|
export type SyncState = SyncAnswer["sync"];
|
|
180
246
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*
|
|
247
|
+
// The sync's bundle never reaches the Machine: `deliver` drops it from the output (ADR-0067).
|
|
248
|
+
/**
|
|
249
|
+
* The output of a `push` Effect: what the `onDone` of its state gets as `event.output`. Every
|
|
250
|
+
* outcome is output, so route on `outcome`:
|
|
251
|
+
*
|
|
252
|
+
* - `pushed`: the remote has the commit. `published` is the commit on the remote, and `source` is
|
|
253
|
+
* the Agent's commit that it comes from. They are the same commit, unless the push was signed or
|
|
254
|
+
* squashed. `previous` is what the remote had before, and `unchanged` is `true` when the remote
|
|
255
|
+
* had the commit already. `squashed` is `signed` or `unsigned` when the commit on the remote is
|
|
256
|
+
* one squashed commit. `via` tells how jr2 found the new commits after a signed push:
|
|
257
|
+
* `ancestry`, `pair`, `cherry` or `force`.
|
|
258
|
+
* - `rejected`: git or the remote refused the push. `stage` tells where, `reason` tells why, and
|
|
259
|
+
* `detail` has the words of git.
|
|
260
|
+
* - `diverged`: the commit on the remote is not in the history of the Agent's branch, and jr2
|
|
261
|
+
* could not pair it with a commit there. Nothing was pushed. `reason` tells why, and `published`
|
|
262
|
+
* and `source` name the two commits.
|
|
263
|
+
* - `unknown`: jr2 cannot tell what the push did. `reason` tells why.
|
|
264
|
+
*
|
|
265
|
+
* Beside `pushed`, `sync` tells what jr2 did to the Agent's branch after the push:
|
|
266
|
+
*
|
|
267
|
+
* - `synced`: the commit on the remote is not the Agent's own, and jr2 moved the branch to the
|
|
268
|
+
* published commits.
|
|
269
|
+
* - `already`: the branch has the published commit already.
|
|
270
|
+
* - `skipped`: the remote has the Agent's own commit, so the branch does not move.
|
|
271
|
+
* - `busy`: a rebase, merge, cherry-pick or revert of the branch is in progress. Nothing changed.
|
|
272
|
+
* - `not-synced`: the branch did not move.
|
|
273
|
+
*
|
|
274
|
+
* With `busy` or `not-synced`, `syncReason` tells why, and jr2 tries again before the next push.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { fail, jr2Setup, push, type PushOutput, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
279
|
+
*
|
|
280
|
+
* type Input = Workspaced<{ sha: string }, "app">;
|
|
281
|
+
*
|
|
282
|
+
* // A push that did not happen (`rejected` or `diverged`) is a different problem from one jr2
|
|
283
|
+
* // cannot see (`unknown`).
|
|
284
|
+
* const notPushed = (output: PushOutput): boolean =>
|
|
285
|
+
* output.outcome === "rejected" || output.outcome === "diverged";
|
|
286
|
+
*
|
|
287
|
+
* const body = jr2Setup({
|
|
288
|
+
* types: {} as { context: Input; input: Input },
|
|
289
|
+
* actors: { publish: push({ pin: true }), fail: fail() },
|
|
290
|
+
* }).createMachine({
|
|
291
|
+
* id: "release",
|
|
292
|
+
* context: ({ input }) => input,
|
|
293
|
+
* on: { "workspace.lost": ".lost" },
|
|
294
|
+
* initial: "publishing",
|
|
295
|
+
* states: {
|
|
296
|
+
* publishing: {
|
|
297
|
+
* invoke: {
|
|
298
|
+
* src: "publish",
|
|
299
|
+
* input: ({ context }) => ({ sha: context.sha }),
|
|
300
|
+
* onDone: [
|
|
301
|
+
* { guard: ({ event }) => event.output.outcome === "pushed", target: "published" },
|
|
302
|
+
* { guard: ({ event }) => notPushed(event.output), target: "notPushed" },
|
|
303
|
+
* { target: "unknown" },
|
|
304
|
+
* ],
|
|
305
|
+
* },
|
|
306
|
+
* },
|
|
307
|
+
* published: { type: "final" },
|
|
308
|
+
* // Not final states: each one fails the run, with its own reason.
|
|
309
|
+
* notPushed: { invoke: { src: "fail", input: { reason: "the remote refused the push" } } },
|
|
310
|
+
* unknown: { invoke: { src: "fail", input: { reason: "jr2 cannot tell if the push happened" } } },
|
|
311
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
312
|
+
* },
|
|
313
|
+
* });
|
|
314
|
+
*
|
|
315
|
+
* export const machine = workspace(body, {
|
|
316
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
317
|
+
* spec: () => ({ branch: "release" }),
|
|
318
|
+
* });
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* @see docs/guides/push-and-sign-a-branch.md
|
|
322
|
+
* @see docs/concepts/effects.md
|
|
323
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
324
|
+
*/
|
|
184
325
|
export type PushOutput =
|
|
185
326
|
| Exclude<PushWireOutput, { outcome: "pushed" }>
|
|
186
327
|
| (Omit<Extract<PushWireOutput, { outcome: "pushed" }>, "bundle"> & { sync: SyncState; syncReason?: string });
|
|
@@ -232,8 +373,8 @@ export function pushStartOf(
|
|
|
232
373
|
};
|
|
233
374
|
}
|
|
234
375
|
|
|
235
|
-
/** A file-body http's start for one run (ADR-0067): the definition's parts, the url's segments, and
|
|
236
|
-
* Worktree file as the body. */
|
|
376
|
+
/** A file-body http's start for one run (ADR-0067): the definition's parts, the url's segments, and
|
|
377
|
+
* the Worktree file as the body. */
|
|
237
378
|
export function fileStartOf(
|
|
238
379
|
parts: HttpParts,
|
|
239
380
|
worktree: string,
|
|
@@ -248,7 +389,8 @@ export function fileStartOf(
|
|
|
248
389
|
return { ...http, body: { file: { worktree, path: run.path, ...(contentType ? { contentType } : {}) } } };
|
|
249
390
|
}
|
|
250
391
|
|
|
251
|
-
/**
|
|
392
|
+
/** The `env` of a `command`: for each variable, `secret("name")` for the value of an Effect
|
|
393
|
+
* secret, or `secret("name").path` for the path of a file that holds it. */
|
|
252
394
|
export type CommandEnv = Readonly<Record<string, { readonly secret: string; readonly path: unknown }>>;
|
|
253
395
|
|
|
254
396
|
export function commandEnvParts(env: CommandEnv): Pick<CommandParts, "env" | "secrets" | "openSecret"> {
|
|
@@ -579,7 +721,8 @@ async function drive<I, O>(
|
|
|
579
721
|
}
|
|
580
722
|
}
|
|
581
723
|
|
|
582
|
-
/** The settled row's Repo Slot, or none when the Workspace does not name one the kind can run on.
|
|
724
|
+
/** The settled row's Repo Slot, or none when the Workspace does not name one the kind can run on.
|
|
725
|
+
*/
|
|
583
726
|
function repoField(spec: KindSpec, ambient: AmbientHandles, slot: string): { repo?: string } {
|
|
584
727
|
try {
|
|
585
728
|
return { repo: spec.repoOf(ambient, slot) };
|
|
@@ -660,9 +803,10 @@ function stoppedText(state: EffectState | undefined): string {
|
|
|
660
803
|
}
|
|
661
804
|
|
|
662
805
|
/** A start over the bytes an Effector reads is sent nowhere, and settles as its kind's own outcome
|
|
663
|
-
* (ADR-0067): an http `refused`; a push `rejected` at stage `start` — nothing pushed; a
|
|
664
|
-
* command `exited` 127 with the reason in its tail, the Effector's own form for a
|
|
665
|
-
* could not start. An Agent's pick parameter — a squash message, an argv part — can
|
|
806
|
+
* (ADR-0067): an http `refused`; a push `rejected` at stage `start` — nothing pushed; a
|
|
807
|
+
* credentialed command `exited` 127 with the reason in its tail, the Effector's own form for a
|
|
808
|
+
* command that could not start. An Agent's pick parameter — a squash message, an argv part — can
|
|
809
|
+
* reach the bound. */
|
|
666
810
|
function tooLarge(req: StartRequest): HttpOutput | PushWireOutput | CommandWireOutput | undefined {
|
|
667
811
|
const bytes = Buffer.byteLength(JSON.stringify(req), "utf8");
|
|
668
812
|
if (bytes <= MAX_START_BYTES) return undefined;
|
|
@@ -728,7 +872,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
728
872
|
build: (input, ambient, slot, pinned, extra) => {
|
|
729
873
|
const repoSlot = slotOf("push", slot, parts.slot, ambient);
|
|
730
874
|
const ref = repoRefOf(ambient, repoSlot);
|
|
731
|
-
const req = pushStartOf(parts, ref, ambient
|
|
875
|
+
const req = pushStartOf(parts, ref, branchOf(ambient), input as { sha?: string; message?: string }, extra?.pairs);
|
|
732
876
|
if (pinned) req.push.pin = pinned;
|
|
733
877
|
return req;
|
|
734
878
|
},
|
|
@@ -746,7 +890,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
746
890
|
const answer = await syncOf(ctx).sync(
|
|
747
891
|
{
|
|
748
892
|
worktree: worktreeOf(ctx),
|
|
749
|
-
branch: ctx.ambient
|
|
893
|
+
branch: branchOf(ctx.ambient),
|
|
750
894
|
published: output.published,
|
|
751
895
|
source: output.source,
|
|
752
896
|
...(typeof output.bundle === "string" ? { bundle: output.bundle } : {}),
|
|
@@ -772,7 +916,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
772
916
|
const answer = await syncOf(ctx).sync(
|
|
773
917
|
{
|
|
774
918
|
worktree: worktreeOf(ctx),
|
|
775
|
-
branch: ctx.ambient
|
|
919
|
+
branch: branchOf(ctx.ambient),
|
|
776
920
|
published: o.published,
|
|
777
921
|
source: o.source,
|
|
778
922
|
...(o.bundle !== undefined ? { bundle: o.bundle } : {}),
|
|
@@ -800,8 +944,9 @@ function syncFields(answer: SyncAnswer): { sync: SyncState; syncReason?: string
|
|
|
800
944
|
|
|
801
945
|
export { lastPushOf };
|
|
802
946
|
|
|
803
|
-
/** An http whose body is a Worktree file (ADR-0067), over an injected port factory — the seam
|
|
804
|
-
* and the unit tests share. It runs in the Sandbox Effector of the Workspace whose body
|
|
947
|
+
/** An http whose body is a Worktree file (ADR-0067), over an injected port factory — the seam
|
|
948
|
+
* `http()` and the unit tests share. It runs in the Sandbox Effector of the Workspace whose body
|
|
949
|
+
* invokes it. */
|
|
805
950
|
export function httpFileWith<
|
|
806
951
|
S extends z.ZodType | undefined = undefined,
|
|
807
952
|
const U extends HttpDefinition<S>["url"] = string,
|
|
@@ -827,7 +972,10 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
827
972
|
kind: "http",
|
|
828
973
|
idempotent: parts.idempotent,
|
|
829
974
|
repeatsPinned: false,
|
|
830
|
-
|
|
975
|
+
// With no Repo Slot the file is in `/work`, which is no Worktree (ADR-0051).
|
|
976
|
+
label: (ambient) =>
|
|
977
|
+
`http ${parts.method} ${parts.target.scheme}://${parts.target.host} ` +
|
|
978
|
+
(Object.keys(ambient.repos).length === 0 && body.slot === undefined ? "(a file of /work)" : "(a Worktree file)"),
|
|
831
979
|
repoOf: (ambient, slot) => slotOf("http", slot, body.slot, ambient),
|
|
832
980
|
build: (input, ambient, slot) => {
|
|
833
981
|
let value: unknown = input;
|
|
@@ -836,8 +984,7 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
836
984
|
if (!parsed.success) throw new Error(`http "${slot}": its input does not match the definition's input schema`);
|
|
837
985
|
value = parsed.data;
|
|
838
986
|
}
|
|
839
|
-
const
|
|
840
|
-
const worktree = posix.relative(WORK_ROOT, ambient.repos[repoSlot]!);
|
|
987
|
+
const worktree = fileRootOf(slot, body.slot, ambient);
|
|
841
988
|
const segments = fromInput<readonly string[]>(body.segments, value);
|
|
842
989
|
return fileStartOf(parts, worktree, {
|
|
843
990
|
path: fromInput<string>(body.path, value)!,
|
|
@@ -847,7 +994,8 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
847
994
|
});
|
|
848
995
|
}
|
|
849
996
|
|
|
850
|
-
/** A command whose env names a secret: it runs in the Sandbox Effector against the pinned commit.
|
|
997
|
+
/** A command whose env names a secret: it runs in the Sandbox Effector against the pinned commit.
|
|
998
|
+
*/
|
|
851
999
|
export function credentialedCommandWith(
|
|
852
1000
|
portFactory: EffectorPortFactory,
|
|
853
1001
|
parts: CommandParts,
|
|
@@ -871,7 +1019,7 @@ export function credentialedCommandWith(
|
|
|
871
1019
|
parts,
|
|
872
1020
|
argvOf(input, slot),
|
|
873
1021
|
ref,
|
|
874
|
-
ambient
|
|
1022
|
+
branchOf(ambient),
|
|
875
1023
|
pinned ?? (approved as string | undefined),
|
|
876
1024
|
);
|
|
877
1025
|
},
|
|
@@ -879,12 +1027,99 @@ export function credentialedCommandWith(
|
|
|
879
1027
|
}
|
|
880
1028
|
|
|
881
1029
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
1030
|
+
* Make a `push` Effect: it pushes the branch of the Workspace to the Binding of its Repo Slot.
|
|
1031
|
+
* Put it in `actors`, and invoke it from the body of a `workspace()`: `actors: { publish: push({
|
|
1032
|
+
* sign: true }) }`.
|
|
1033
|
+
*
|
|
1034
|
+
* It runs in the Sandbox Effector of the Workspace, with the push credential of the
|
|
1035
|
+
* `git.credentials` entry that the Binding matches. The Agent never reads that credential. It
|
|
1036
|
+
* pushes one commit: the tip of the Agent's branch when the Effect starts, or, with `pin`, the
|
|
1037
|
+
* commit that the input names. After an Orchestrator restart, jr2 pushes only that same commit
|
|
1038
|
+
* again. With `sign` or `squash`, the commit on the remote is not the Agent's own. Then jr2 moves
|
|
1039
|
+
* the Agent's branch to it.
|
|
1040
|
+
*
|
|
1041
|
+
* Every outcome is output (`PushOutput`): `pushed`, `rejected`, `diverged` or `unknown`. Each
|
|
1042
|
+
* field of {@link PushOptions} tells what it refuses. When it starts, it also refuses a push
|
|
1043
|
+
* outside a Workspace: `push "<slot>" runs in the Sandbox Effector of a Workspace, and its state is
|
|
1044
|
+
* not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
|
|
1045
|
+
*
|
|
1046
|
+
* @param options - Optional. Each field tells what it does.
|
|
1047
|
+
* @returns An Effect, for `actors`.
|
|
1048
|
+
*
|
|
1049
|
+
* @example
|
|
1050
|
+
* ```ts
|
|
1051
|
+
* import { z } from "zod";
|
|
1052
|
+
* import {
|
|
1053
|
+
* agent,
|
|
1054
|
+
* defineEvent,
|
|
1055
|
+
* fail,
|
|
1056
|
+
* jr2Setup,
|
|
1057
|
+
* push,
|
|
1058
|
+
* workspace,
|
|
1059
|
+
* type Workspaced,
|
|
1060
|
+
* } from "@jr2/orchestrator";
|
|
1061
|
+
*
|
|
1062
|
+
* const committed = defineEvent({
|
|
1063
|
+
* name: "committed",
|
|
1064
|
+
* description: "Say that the fix is committed.",
|
|
1065
|
+
* input: z.object({}),
|
|
1066
|
+
* });
|
|
1067
|
+
*
|
|
1068
|
+
* type Input = Workspaced<{}, "app">;
|
|
1069
|
+
*
|
|
1070
|
+
* const body = jr2Setup({
|
|
1071
|
+
* types: {} as { context: Input; input: Input },
|
|
1072
|
+
* events: [committed],
|
|
1073
|
+
* actors: {
|
|
1074
|
+
* coder: agent({
|
|
1075
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
1076
|
+
* instructions: "You are a careful engineer.",
|
|
1077
|
+
* }),
|
|
1078
|
+
* publish: push({ sign: true }),
|
|
1079
|
+
* fail: fail(),
|
|
1080
|
+
* },
|
|
1081
|
+
* }).createMachine({
|
|
1082
|
+
* id: "fix-test",
|
|
1083
|
+
* context: ({ input }) => input,
|
|
1084
|
+
* on: { "workspace.lost": ".failed", "agent.fault": ".failed" },
|
|
1085
|
+
* initial: "coding",
|
|
1086
|
+
* states: {
|
|
1087
|
+
* coding: {
|
|
1088
|
+
* invoke: { src: "coder", input: { prompt: "Fix the failing test, and commit the fix." } },
|
|
1089
|
+
* // The Agent moves the Machine on with a pick. An Agent's actor never ends, so `onDone`
|
|
1090
|
+
* // never fires.
|
|
1091
|
+
* on: { committed: "publishing" },
|
|
1092
|
+
* },
|
|
1093
|
+
* publishing: {
|
|
1094
|
+
* invoke: {
|
|
1095
|
+
* src: "publish",
|
|
1096
|
+
* onDone: [
|
|
1097
|
+
* { guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
1098
|
+
* { target: "failed" },
|
|
1099
|
+
* ],
|
|
1100
|
+
* },
|
|
1101
|
+
* },
|
|
1102
|
+
* done: { type: "final" },
|
|
1103
|
+
* // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
1104
|
+
* failed: { invoke: { src: "fail", input: { reason: "the fix was not pushed" } } },
|
|
1105
|
+
* },
|
|
1106
|
+
* });
|
|
1107
|
+
*
|
|
1108
|
+
* export const machine = workspace(body, {
|
|
1109
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
1110
|
+
* spec: () => ({ branch: "fix-test" }),
|
|
1111
|
+
* });
|
|
1112
|
+
* ```
|
|
1113
|
+
*
|
|
1114
|
+
* @see docs/guides/push-and-sign-a-branch.md
|
|
1115
|
+
* @see docs/concepts/effects.md
|
|
1116
|
+
* @see docs/guides/give-the-cluster-git-credentials.md
|
|
1117
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1118
|
+
* @see docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md
|
|
886
1119
|
*/
|
|
887
1120
|
export function push<const O extends PushOptions>(options?: O): EffectLogic<PushInput<O> | void, PushOutput> {
|
|
1121
|
+
// After an Orchestrator restart, a push is sent again with only the commit its intent row holds
|
|
1122
|
+
// (ADR-0067): a run is a function of the commit, never of the Agent's tip now.
|
|
888
1123
|
return pushWith(sandboxEffectorPort, options);
|
|
889
1124
|
}
|
|
890
1125
|
|
package/src/sandbox-kube.ts
CHANGED
|
@@ -1394,7 +1394,9 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
|
|
|
1394
1394
|
const endpoint = sandbox?.status?.endpoint;
|
|
1395
1395
|
if (!endpoint) throw new Error(`Sandbox "${req.name}" has no endpoint to attach through — is it gone?`);
|
|
1396
1396
|
if (req.repos.length === 0) {
|
|
1397
|
-
throw new Error(
|
|
1397
|
+
throw new Error(
|
|
1398
|
+
"the attach names no Repo Slot — nothing to attach (a Workspace with no Repo Slot skips the attach, ADR-0051)",
|
|
1399
|
+
);
|
|
1398
1400
|
}
|
|
1399
1401
|
const body: AttachRequest = {
|
|
1400
1402
|
slots: req.repos.map((r) => {
|
package/src/secret-placement.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// - the Instance Harness: the model keys of the `"none"` Agents it hosts, and nothing else (they
|
|
7
7
|
// cannot execute code, ADR-0031, so nothing else is theirs to use);
|
|
8
8
|
// - an Effector: what its Effects name with `secret("name")` — the Instance Effector what its
|
|
9
|
-
// `http` Effects name
|
|
9
|
+
// `http` Effects name and the token of each `dispatch` with a target (ADR-0071; a dispatch with
|
|
10
|
+
// no target runs in the Orchestrator's own process and names nothing); a Workspace's Sandbox Effector what its file-body `http`s and
|
|
10
11
|
// credentialed `command`s name. The push credential its `push`es spend is not placed here: the provision
|
|
11
12
|
// names the entry the slot's url matches on the Sandbox, by reference (git-push.ts, ADR-0051).
|
|
12
13
|
//
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
type HeldLookups,
|
|
28
29
|
} from "./held-secrets.ts";
|
|
29
30
|
import type { JR2Config } from "./config.ts";
|
|
31
|
+
import { needsASlot } from "./effect.ts";
|
|
30
32
|
import { declaredPushKinds, pushRefusal } from "./git-push.ts";
|
|
31
33
|
import { INSTANCE_HARNESS_HELD_POD } from "./names.ts";
|
|
32
34
|
import { routeText, type CarriedEffect, type WorkspacePod } from "./parts.ts";
|
|
@@ -140,7 +142,29 @@ export function refuseEffectSecrets(config: JR2Config, walk: PlacementWalk): voi
|
|
|
140
142
|
// An empty value is refused later with the list's own rule (`sourceOf`): it is an unset variable.
|
|
141
143
|
if (namedBy.has(e.name) && e.value) shortRefusal(e, e.value.length);
|
|
142
144
|
}
|
|
143
|
-
for (const ws of walk.workspaces)
|
|
145
|
+
for (const ws of walk.workspaces) {
|
|
146
|
+
refuseWithNoSlot(ws);
|
|
147
|
+
pushesOf(config, ws);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A Workspace with no Repo Slot (ADR-0051) has no branch and no commit, so the two Sandbox Effector kinds that
|
|
153
|
+
* run on a commit of a slot's branch have none: a `push`, and a `command` with an Effect secret
|
|
154
|
+
* (ADR-0067). Refused here, before anything is built, as the invoke would refuse them mid-run. A
|
|
155
|
+
* file-body `http` reads `/work` and stays.
|
|
156
|
+
*/
|
|
157
|
+
function refuseWithNoSlot(ws: PlacedWorkspace): void {
|
|
158
|
+
if (!ws.pod.noSlot) return;
|
|
159
|
+
for (const effect of ws.pod.effects) {
|
|
160
|
+
// Every `command` placed in a Sandbox Effector names a secret: the others run beside the Agent.
|
|
161
|
+
const why = needsASlot(effect.kind);
|
|
162
|
+
if (why === undefined) continue;
|
|
163
|
+
throw new Error(
|
|
164
|
+
`workflow "${ws.workflow}": ${effect.kind} "${effect.name}" runs in ${workspaceLabel(ws)}, which has no Repo ` +
|
|
165
|
+
`Slot: ${why} (ADR-0051)`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
144
168
|
}
|
|
145
169
|
|
|
146
170
|
/**
|
|
@@ -153,7 +177,7 @@ export function secretPlacementsOf(
|
|
|
153
177
|
walk: PlacementWalk,
|
|
154
178
|
lookups: Pick<HeldLookups, "secretKeys" | "valueLength"> = {},
|
|
155
179
|
): SecretPlacements {
|
|
156
|
-
const { entries, namedBy,
|
|
180
|
+
const { entries, namedBy, instancePlaced } = namesOf(config, walk);
|
|
157
181
|
const { pods, redundant } = heldPodsOf(config, walk, new Set(entries.map((e) => e.name)));
|
|
158
182
|
const claimed = new Set(pods.flatMap((p) => p.names));
|
|
159
183
|
|
|
@@ -207,12 +231,12 @@ export function secretPlacementsOf(
|
|
|
207
231
|
report.push(`${label}: secrets lists ${name}, which its Agents' provider claims already — redundant`);
|
|
208
232
|
}
|
|
209
233
|
|
|
210
|
-
const instanceSecrets = new Set(
|
|
234
|
+
const instanceSecrets = new Set(instancePlaced.flatMap((p) => p.effect.secrets));
|
|
211
235
|
const instance =
|
|
212
|
-
|
|
236
|
+
instancePlaced.length === 0
|
|
213
237
|
? undefined
|
|
214
238
|
: effectorPlan(
|
|
215
|
-
|
|
239
|
+
instancePlaced,
|
|
216
240
|
secrets.filter((s) => instanceSecrets.has(s.name)),
|
|
217
241
|
values,
|
|
218
242
|
);
|
|
@@ -267,6 +291,7 @@ function sandboxPlanOf(
|
|
|
267
291
|
x.scheme === t.scheme && x.host === t.host && x.port === t.port && x.secrets.join() === t.secrets.join();
|
|
268
292
|
if (!targets.some(same)) targets.push(t);
|
|
269
293
|
}
|
|
294
|
+
refuseWithNoSlot(ws);
|
|
270
295
|
const pushes = pushesOf(config, ws);
|
|
271
296
|
const commands = ws.pod.effects.some((e) => e.kind === "command" && (e.secrets?.length ?? 0) > 0);
|
|
272
297
|
return {
|
|
@@ -322,10 +347,12 @@ function pushesOf(config: JR2Config, ws: PlacedWorkspace): SandboxPush[] {
|
|
|
322
347
|
return out;
|
|
323
348
|
}
|
|
324
349
|
|
|
325
|
-
function effectorPlan(
|
|
350
|
+
function effectorPlan(placed: InstancePlaced[], secrets: EffectSecret[], values: Record<string, string>): EffectorPlan {
|
|
326
351
|
const targets: EffectorTargetConfig[] = [];
|
|
327
|
-
for (const { effect } of
|
|
328
|
-
// An Open url has no target, so it allows nothing: `jr2 up` refuses
|
|
352
|
+
for (const { effect } of placed) {
|
|
353
|
+
// An Open url or an Open dispatch target has no target, so it allows nothing: `jr2 up` refuses
|
|
354
|
+
// it before this (ADR-0054). A dispatch's target is its origin, its one secret the token
|
|
355
|
+
// (ADR-0071).
|
|
329
356
|
if (effect.target === undefined) continue;
|
|
330
357
|
const t = { ...effect.target, secrets: [...effect.secrets].sort() };
|
|
331
358
|
const same = (x: EffectorTargetConfig) =>
|
|
@@ -337,12 +364,17 @@ function effectorPlan(http: HttpPlaced[], secrets: EffectSecret[], values: Recor
|
|
|
337
364
|
secrets,
|
|
338
365
|
values,
|
|
339
366
|
targets,
|
|
340
|
-
|
|
341
|
-
|
|
367
|
+
// The grace a rollout gives a replica covers the http Effects alone (ADR-0071): a dispatch is
|
|
368
|
+
// idempotent by construction, so a `wait` a rollout cuts is sent again under its key and its
|
|
369
|
+
// poll resumes, and a wait of hours never stretches the Effector's grace.
|
|
370
|
+
longestTimeoutMs: Math.max(0, ...placed.flatMap((p) => (p.effect.kind === "http" ? [p.effect.timeoutMs] : []))),
|
|
371
|
+
effects: placed.map((p) => `${p.workflow}/${p.effect.name}`),
|
|
342
372
|
};
|
|
343
373
|
}
|
|
344
374
|
|
|
345
|
-
|
|
375
|
+
/** An Effect the Instance Effector runs: an http one with no Worktree file, or a dispatch with a
|
|
376
|
+
* target (ADR-0068, ADR-0071). */
|
|
377
|
+
type InstancePlaced = { workflow: string; effect: Extract<CarriedEffect, { kind: "http" | "dispatch" }> };
|
|
346
378
|
|
|
347
379
|
/** A Workspace as a human reads it: its workflow and the placement route to it, an inline hop as
|
|
348
380
|
* `invoked inline in state <id>`. */
|
|
@@ -423,11 +455,14 @@ function namesOf(config: JR2Config, walk: PlacementWalk) {
|
|
|
423
455
|
}
|
|
424
456
|
byName.set(e.name, e);
|
|
425
457
|
}
|
|
426
|
-
// Every Effect that names a secret names it, whatever its kind: an `http` one
|
|
427
|
-
// Effector, a file-body `http` and a credentialed `command` in their
|
|
458
|
+
// Every Effect that names a secret names it, whatever its kind: an `http` one and a `dispatch`'s
|
|
459
|
+
// token in the Instance Effector, a file-body `http` and a credentialed `command` in their
|
|
460
|
+
// Workspace's Sandbox Effector. A dispatch with no target is placed "orchestrator" and names none
|
|
461
|
+
// (ADR-0071).
|
|
428
462
|
const namedBy = new Map<string, Array<{ label: string; where: string }>>();
|
|
429
|
-
const
|
|
430
|
-
(p): p is
|
|
463
|
+
const instancePlaced = walk.effects.filter(
|
|
464
|
+
(p): p is InstancePlaced =>
|
|
465
|
+
(p.effect.kind === "http" || p.effect.kind === "dispatch") && p.effect.placement === "instance",
|
|
431
466
|
);
|
|
432
467
|
// Keyed by the Effect's placement route and slot name, as workspacePodsOf keys a pod's Effects:
|
|
433
468
|
// two slots of one name in one workflow — one in a workspace(), one outside it — are two Effects,
|
|
@@ -463,7 +498,7 @@ function namesOf(config: JR2Config, walk: PlacementWalk) {
|
|
|
463
498
|
namedBy.set(name, [...(namedBy.get(name) ?? []), { label, where }]);
|
|
464
499
|
}
|
|
465
500
|
}
|
|
466
|
-
return { entries, namedBy,
|
|
501
|
+
return { entries, namedBy, instancePlaced };
|
|
467
502
|
}
|
|
468
503
|
|
|
469
504
|
/** An Effect secret shorter than 8 characters is refused, and the refusal says why. */
|
package/src/server.ts
CHANGED
|
@@ -295,6 +295,9 @@ export async function serverMain(opts: ServerMainOptions): Promise<RunningInstan
|
|
|
295
295
|
instanceHarness: namespace
|
|
296
296
|
? `http://${INSTANCE_HARNESS_SERVICE}.${namespace}.svc:${INSTANCE_HARNESS_PORT}`
|
|
297
297
|
: undefined,
|
|
298
|
+
// What a foreign dispatch names this Orchestrator by at the callee's door (ADR-0071): its
|
|
299
|
+
// namespace, which is stable and not secret — never a run's Instance ID.
|
|
300
|
+
...(namespace ? { orchestratorName: namespace } : {}),
|
|
298
301
|
// Where an http Effect runs (ADR-0068): the Instance Effector's headless Service. `jr2 up`
|
|
299
302
|
// converges it whenever a registered Machine carries an http Effect.
|
|
300
303
|
...(namespace ? { instanceEffector: `${INSTANCE_EFFECTOR}.${namespace}.svc:${EFFECTOR_PORT}` } : {}),
|