@jr2/orchestrator 0.4.1 → 0.6.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.
- 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 +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -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 +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -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 +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -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 +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -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 +206 -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 +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -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 +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -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-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -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 +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -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 +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -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 +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -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 +95 -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-lateroutcome.md +21 -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-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -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 +22 -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 +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -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 +84 -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 +24 -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 +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -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 +27 -0
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -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 +175 -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 +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -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 +287 -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 +101 -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 +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -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 +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -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 +193 -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 +126 -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 +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -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 +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -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 +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -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 +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -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 +334 -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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- 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/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
package/src/effector-wire.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
15
15
|
import { lookup } from "node:dns/promises";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import type { LaterOutcome } from "./effect.ts";
|
|
17
18
|
|
|
18
19
|
// --- The request (POST /effects/<id>) ------------------------------------------------------------
|
|
19
20
|
//
|
|
@@ -21,11 +22,14 @@ import { z } from "zod";
|
|
|
21
22
|
|
|
22
23
|
export const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
|
|
23
24
|
export const HttpMethodShape = z.enum(HTTP_METHODS);
|
|
25
|
+
/** The method of an `http` Effect: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`. */
|
|
24
26
|
export type HttpMethod = z.infer<typeof HttpMethodShape>;
|
|
25
27
|
|
|
26
28
|
/** One part of a string the Effector builds: literal text, or a secret it holds, by name. A
|
|
27
29
|
* secret's value is never on the wire. */
|
|
28
30
|
export const TextPartShape = z.union([z.strictObject({ text: z.string() }), z.strictObject({ secret: z.string() })]);
|
|
31
|
+
/** One part of a {@link Text}: `{ text }`, literal text, or `{ secret }`, the name of an Effect
|
|
32
|
+
* secret. The `text` template makes the parts. */
|
|
29
33
|
export type TextPart = z.infer<typeof TextPartShape>;
|
|
30
34
|
|
|
31
35
|
export const WireAuthShape = z.union([
|
|
@@ -71,7 +75,8 @@ const HEADER_NAME = /^[a-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
|
71
75
|
export const isEffectHeader = (name: string): boolean =>
|
|
72
76
|
HEADER_NAME.test(name) && !FRAMING_HEADERS.has(name) && !name.startsWith("proxy-");
|
|
73
77
|
|
|
74
|
-
/** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector
|
|
78
|
+
/** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector
|
|
79
|
+
* serves. */
|
|
75
80
|
export const HttpStartShape = z.strictObject({
|
|
76
81
|
kind: z.literal("http"),
|
|
77
82
|
method: HttpMethodShape,
|
|
@@ -100,10 +105,11 @@ export const PairShape = z.strictObject({
|
|
|
100
105
|
});
|
|
101
106
|
export type Pair = z.infer<typeof PairShape>;
|
|
102
107
|
|
|
103
|
-
/** A `push` start:
|
|
108
|
+
/** A `push` start: a cut Worktree's branch to the Repo Slot's Binding, with the push credential of
|
|
104
109
|
* the `git.credentials` entry the url falls under — the Effector's config holds the entries, and
|
|
105
110
|
* matches the url as the fence does (`credential` names one by its `match` instead). `pin` is the
|
|
106
|
-
* commit; absent, the Effector reads the
|
|
111
|
+
* commit; absent, the Effector reads the branch's tip and its answer carries it (`pinned`).
|
|
112
|
+
*/
|
|
107
113
|
export const PushStartShape = z.strictObject({
|
|
108
114
|
kind: z.literal("push"),
|
|
109
115
|
push: z.strictObject({
|
|
@@ -142,8 +148,71 @@ export const CommandStartShape = z.strictObject({
|
|
|
142
148
|
});
|
|
143
149
|
export type CommandStartRequest = z.infer<typeof CommandStartShape>;
|
|
144
150
|
|
|
145
|
-
/**
|
|
146
|
-
export const
|
|
151
|
+
/** A string the Effector builds, as parts (the `text` template's wire form). */
|
|
152
|
+
export const TextShape = z.array(TextPartShape);
|
|
153
|
+
|
|
154
|
+
/** A secret the Effector holds, by name. */
|
|
155
|
+
export const SecretRefShape = z.strictObject({ secret: z.string() });
|
|
156
|
+
|
|
157
|
+
/** A Workflow's name as `jr2 run` names it (ADR-0071): a Machine's filename stem, or
|
|
158
|
+
* `<wrapper>/<name>` for a named Chart. The Go side's `Validate` holds the same rule. */
|
|
159
|
+
export const WORKFLOW_NAME = /^[a-z0-9][a-z0-9-]*(\/[a-z0-9][a-z0-9-]*)?$/;
|
|
160
|
+
|
|
161
|
+
/** A dispatch target's origin: a scheme and a host — a DNS name or IPv4 address, or a bracketed
|
|
162
|
+
* IPv6 address — a port when it is not the scheme's, and no path, query, fragment, userinfo or any
|
|
163
|
+
* other character no origin has — the Effector appends the door's path (ADR-0071). The Go side's
|
|
164
|
+
* `origin` is the same rule. */
|
|
165
|
+
export const ORIGIN = /^https?:\/\/(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9.-]+)(:[0-9]{1,5})?$/;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Which run started a run, and its place in the chain (ADR-0071): the caller's run and slot, the
|
|
169
|
+
* caller's ledger row id (`effect`, the door's idempotency key), the dispatched run's depth — one
|
|
170
|
+
* below its caller, stated by the caller and recorded as sent, so at least 1 — and, for a caller
|
|
171
|
+
* on another Orchestrator, that Instance as opaque text.
|
|
172
|
+
*/
|
|
173
|
+
export const DispatchedByShape = z.strictObject({
|
|
174
|
+
run: z.string(),
|
|
175
|
+
slot: z.string(),
|
|
176
|
+
effect: z.string(),
|
|
177
|
+
depth: z.number().int().positive(),
|
|
178
|
+
instance: z.string().optional(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A `dispatch` start (ADR-0071), which only the Instance Effector serves: a run of `workflow` on the
|
|
183
|
+
* Orchestrator at `url` (its origin, one text part), the body `input` whole, the Instance token as a
|
|
184
|
+
* bearer by name, `dispatchedBy` as the `jr2-dispatched-by` header — `instance` set, since the
|
|
185
|
+
* caller is foreign by construction — and, with `wait`, a poll of `GET /runs/<id>` until the run
|
|
186
|
+
* ends or `timeoutMs` passes.
|
|
187
|
+
*/
|
|
188
|
+
export const DispatchStartShape = z.strictObject({
|
|
189
|
+
kind: z.literal("dispatch"),
|
|
190
|
+
url: TextShape.refine(
|
|
191
|
+
(parts) => parts.length === 1 && "text" in parts[0]! && ORIGIN.test(parts[0].text),
|
|
192
|
+
"a dispatch url is one text part, an origin: http(s)://host[:port]",
|
|
193
|
+
),
|
|
194
|
+
workflow: z.string().regex(WORKFLOW_NAME),
|
|
195
|
+
input: z.record(z.string(), z.unknown()),
|
|
196
|
+
auth: z.strictObject({ bearer: SecretRefShape }),
|
|
197
|
+
// Every id non-empty, as the door's header shape requires (http.ts): an empty one would be the
|
|
198
|
+
// door's 400, which reads as a defect of the input. The effect is a ledger row id, the key. The Go
|
|
199
|
+
// side's `validateDispatch` holds the same rule.
|
|
200
|
+
dispatchedBy: DispatchedByShape.extend({
|
|
201
|
+
run: z.string().min(1),
|
|
202
|
+
slot: z.string().min(1),
|
|
203
|
+
effect: z
|
|
204
|
+
.string()
|
|
205
|
+
.regex(/^[A-Za-z0-9._-]{1,128}$/)
|
|
206
|
+
.refine((id) => id !== "." && id !== ".."),
|
|
207
|
+
instance: z.string().min(1),
|
|
208
|
+
}),
|
|
209
|
+
wait: z.boolean(),
|
|
210
|
+
timeoutMs: z.number().int().positive(),
|
|
211
|
+
});
|
|
212
|
+
export type DispatchStartRequest = z.infer<typeof DispatchStartShape>;
|
|
213
|
+
|
|
214
|
+
/** Every start: the http kinds, a push, a credentialed command, a dispatch. */
|
|
215
|
+
export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape, DispatchStartShape]);
|
|
147
216
|
export type StartRequest = z.infer<typeof StartRequestShape>;
|
|
148
217
|
|
|
149
218
|
/** The most bytes of a start's body the Effector reads (its `maxRequestBody`). A longer body
|
|
@@ -175,17 +244,70 @@ export const HttpOutputShape = z.union([
|
|
|
175
244
|
HttpResponseShape,
|
|
176
245
|
z.strictObject({ outcome: z.enum(["unreachable", "refused", "unknown"]), reason: z.string() }),
|
|
177
246
|
]);
|
|
178
|
-
|
|
247
|
+
/**
|
|
248
|
+
* The output of an `http` Effect: what the `onDone` of its state gets as `event.output`. Every
|
|
249
|
+
* outcome is output. A 4xx or 5xx status is output too. Route on `outcome`:
|
|
250
|
+
*
|
|
251
|
+
* - `response`: an answer came back. `status` is its HTTP status. `headers`, `contentType` and
|
|
252
|
+
* `size` tell about the body. `body` is there when the body is text: at most `maxBody` bytes
|
|
253
|
+
* (`truncated` is `true` when jr2 cut it). A body that is not text gives only its size and
|
|
254
|
+
* content type. jr2 replaces each value of an Effect secret that the Effector holds, and its
|
|
255
|
+
* usual encodings, with `[secret:<name>]`, in the body and the headers.
|
|
256
|
+
* - `unreachable`: the call did not go out, for example because the name did not resolve, or the
|
|
257
|
+
* connection or TLS failed. `reason` tells why.
|
|
258
|
+
* - `refused`: jr2 or the Effector refused the call, and nothing was sent. For example, a path
|
|
259
|
+
* segment that is `.`, `..` or empty, or a request that is too large. `reason` tells why.
|
|
260
|
+
* - `unknown`: the call can have gone out, but no answer came, or jr2 cannot tell after a restart.
|
|
261
|
+
* `reason` tells why. jr2 does not send it again unless it is marked `idempotent`.
|
|
262
|
+
*
|
|
263
|
+
* The union is open (`LaterOutcome`): give the rest a last arm with no guard.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* import { fail, http, jr2Setup, type HttpOutput } from "@jr2/orchestrator";
|
|
268
|
+
*
|
|
269
|
+
* const ok = (output: HttpOutput): boolean => output.outcome === "response" && output.status < 300;
|
|
270
|
+
*
|
|
271
|
+
* export const machine = jr2Setup({
|
|
272
|
+
* actors: { health: http({ method: "GET", url: "https://app.example.com/healthz" }), fail: fail() },
|
|
273
|
+
* }).createMachine({
|
|
274
|
+
* id: "health",
|
|
275
|
+
* initial: "checking",
|
|
276
|
+
* states: {
|
|
277
|
+
* checking: {
|
|
278
|
+
* invoke: {
|
|
279
|
+
* src: "health",
|
|
280
|
+
* onDone: [
|
|
281
|
+
* { guard: ({ event }) => ok(event.output), target: "healthy" },
|
|
282
|
+
* { target: "unhealthy" },
|
|
283
|
+
* ],
|
|
284
|
+
* },
|
|
285
|
+
* },
|
|
286
|
+
* healthy: { type: "final" },
|
|
287
|
+
* // Not a final state: the run ends `failed`, and `jr2 run` exits 1.
|
|
288
|
+
* unhealthy: { invoke: { src: "fail", input: { reason: "the app is not healthy" } } },
|
|
289
|
+
* },
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @see docs/concepts/effects.md
|
|
294
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
295
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
296
|
+
*/
|
|
297
|
+
export type HttpOutput = z.infer<typeof HttpOutputShape> | LaterOutcome;
|
|
179
298
|
|
|
180
299
|
/**
|
|
181
300
|
* A `push` Effect's output (ADR-0067). `pushed`: the published commit, its source (the Agent's
|
|
182
301
|
* commit), what the remote held before, how the new commits were found after a signed push
|
|
183
|
-
* (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
302
|
+
* (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `newCommits`
|
|
303
|
+
* (how many commits the published commit adds to what jr2 found held, the remote's branch and the
|
|
304
|
+
* Repo cache; 0 when jr2 found the published commit already held, which the Agent reads in words,
|
|
305
|
+
* menu.ts), `squashed` when the published
|
|
306
|
+
* commit is one squashed commit (`signed | unsigned`; absent for signed copies; what the published
|
|
307
|
+
* commit is, an earlier push's included, never only this push's options), and — when the published
|
|
308
|
+
* commit is not the Agent's own — the published commits as a bundle for the sync. `rejected`: a
|
|
309
|
+
* stage and git's words. `diverged`: the published commit is not in the Agent's history and
|
|
310
|
+
* nothing paired it; nothing was pushed.
|
|
189
311
|
*/
|
|
190
312
|
export const PushOutputShape = z.union([
|
|
191
313
|
z.strictObject({
|
|
@@ -195,6 +317,9 @@ export const PushOutputShape = z.union([
|
|
|
195
317
|
previous: z.string().optional(),
|
|
196
318
|
via: z.string().optional(),
|
|
197
319
|
unchanged: z.boolean().optional(),
|
|
320
|
+
// Optional: a live Sandbox keeps the runtime it started with (ADR-0037), so its older Effector
|
|
321
|
+
// sends no count after `jr2 up`. With no count the Harness makes no claim (menu.ts).
|
|
322
|
+
newCommits: z.number().int().nonnegative().optional(),
|
|
198
323
|
squashed: z.enum(["signed", "unsigned"]).optional(),
|
|
199
324
|
bundle: z.string().optional(),
|
|
200
325
|
}),
|
|
@@ -207,6 +332,7 @@ export const PushOutputShape = z.union([
|
|
|
207
332
|
z.strictObject({ outcome: z.literal("diverged"), reason: z.string(), published: z.string(), source: z.string() }),
|
|
208
333
|
z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
|
|
209
334
|
]);
|
|
335
|
+
/** @internal */
|
|
210
336
|
export type PushWireOutput = z.infer<typeof PushOutputShape>;
|
|
211
337
|
|
|
212
338
|
/** A credentialed command's output, as the command kind's (ADR-0067), and `dirty` when the
|
|
@@ -233,6 +359,29 @@ export const CommandOutputShape = z.union([
|
|
|
233
359
|
]);
|
|
234
360
|
export type CommandWireOutput = z.infer<typeof CommandOutputShape>;
|
|
235
361
|
|
|
362
|
+
/**
|
|
363
|
+
* A `dispatch` Effect's output on the wire (ADR-0071): its own outcome set. `settled.status` is the
|
|
364
|
+
* callee's status as the door served it, in the store's words, and `output` the callee Machine's,
|
|
365
|
+
* passed through scrubbed; `refused` is the door's status and its report, told from an http
|
|
366
|
+
* `refused` (which has a `reason`) by its fields.
|
|
367
|
+
*/
|
|
368
|
+
export const DispatchOutputShape = z.discriminatedUnion("outcome", [
|
|
369
|
+
z.strictObject({ outcome: z.literal("started"), run: z.string() }),
|
|
370
|
+
z.strictObject({
|
|
371
|
+
outcome: z.literal("settled"),
|
|
372
|
+
run: z.string(),
|
|
373
|
+
// The store's words (ADR-0025) and the timeout's `live`, as `DispatchOutput` types them for the
|
|
374
|
+
// Machine: a door that serves another word (another Kit version) is a malformed answer, the
|
|
375
|
+
// invoke's error, never a status the Machine's type does not name (ADR-0071).
|
|
376
|
+
status: z.enum(["done", "failed", "error", "cancelled", "lost", "drifted", "live"]),
|
|
377
|
+
output: z.unknown().optional(),
|
|
378
|
+
}),
|
|
379
|
+
z.strictObject({ outcome: z.literal("refused"), status: z.number().int(), report: z.array(z.string()) }),
|
|
380
|
+
z.strictObject({ outcome: z.literal("unreachable"), reason: z.string() }),
|
|
381
|
+
z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
|
|
382
|
+
]);
|
|
383
|
+
export type DispatchWireOutput = z.infer<typeof DispatchOutputShape>;
|
|
384
|
+
|
|
236
385
|
/** The replica that holds an Effect, and where to poll it. */
|
|
237
386
|
export const ReplicaShape = z.strictObject({ name: z.string(), address: z.string() });
|
|
238
387
|
export type Replica = z.infer<typeof ReplicaShape>;
|
|
@@ -245,7 +394,10 @@ export const EffectStateShape = z.strictObject({
|
|
|
245
394
|
/** The commit a push or a credentialed command runs against, read when it started: the
|
|
246
395
|
* Orchestrator writes it to the ledger's intent row. */
|
|
247
396
|
pinned: z.string().optional(),
|
|
248
|
-
|
|
397
|
+
/** The run a dispatch started, once the door answered and while its wait runs: the Orchestrator
|
|
398
|
+
* writes it to the ledger's intent row, so the caller names its callee during the wait (ADR-0071). */
|
|
399
|
+
run: z.string().optional(),
|
|
400
|
+
output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape]).optional(),
|
|
249
401
|
});
|
|
250
402
|
export type EffectState = z.infer<typeof EffectStateShape>;
|
|
251
403
|
|
package/src/fail.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// The `fail()` Part (ADR-0073): the end that a Machine chooses for work that did not succeed — a
|
|
2
|
+
// Failure (GLOSSARY.md). It is a Part and not an Effect, because it does no work outside the
|
|
3
|
+
// Machine: its actor throws an error that carries a brand, and xstate sends that error up through
|
|
4
|
+
// each parent Machine by its own rules. An `onError` on the way catches it; `isFailure` tells it
|
|
5
|
+
// from a defect. When no state catches it, the run host ends the run `failed`.
|
|
6
|
+
//
|
|
7
|
+
// The brand is a registry symbol (`Symbol.for`), not a module symbol, for the reason the walk is
|
|
8
|
+
// structural (parts.ts): an Instance resolves its OWN `@jr2/orchestrator`, so the copy that throws
|
|
9
|
+
// and the copy that reads the error (the run host, the CLI's walk) can be two module instances. The
|
|
10
|
+
// brand is not exported: an author makes a Failure only with `fail()`.
|
|
11
|
+
|
|
12
|
+
import { fromPromise, type PromiseActorLogic } from "xstate";
|
|
13
|
+
|
|
14
|
+
const FAILURE: unique symbol = Symbol.for("jr2.failure") as never;
|
|
15
|
+
const FAIL_SLOT: unique symbol = Symbol.for("jr2.fail.slot") as never;
|
|
16
|
+
|
|
17
|
+
/** What a Failure carries: `reason`, text for a person, and `detail`, optional JSON for a machine
|
|
18
|
+
* reader. The input of `fail()` and the output of a run that ends `failed`. */
|
|
19
|
+
export type FailureInput = { reason: string; detail?: unknown };
|
|
20
|
+
|
|
21
|
+
/** A Failure as `isFailure` narrows an error to it: the payload's fields, on the error itself. */
|
|
22
|
+
export type Failure = { readonly reason: string; readonly detail?: unknown };
|
|
23
|
+
|
|
24
|
+
/** Make the branded error. Only this module makes one. */
|
|
25
|
+
function failure(input: FailureInput): Error & Failure {
|
|
26
|
+
const error = new Error(input.reason) as Error & Failure & { [FAILURE]: true };
|
|
27
|
+
error.name = "Failure";
|
|
28
|
+
Object.defineProperty(error, "reason", { value: input.reason, enumerable: true });
|
|
29
|
+
if (input.detail !== undefined) Object.defineProperty(error, "detail", { value: input.detail, enumerable: true });
|
|
30
|
+
Object.defineProperty(error, FAILURE, { value: true });
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Refuse an input that is not a Failure's. A defect of the Machine, not a Failure: it ends the run
|
|
35
|
+
* `error`, with this text as the fault. */
|
|
36
|
+
function checked(raw: unknown): FailureInput {
|
|
37
|
+
const input = raw as Partial<FailureInput> | undefined;
|
|
38
|
+
if (typeof input !== "object" || input === null || typeof input.reason !== "string" || input.reason === "") {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`fail: the input must be { reason: string; detail?: unknown } with a reason that is not empty (got ${describe(raw)}) — ` +
|
|
41
|
+
`invoke: { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return input.detail === undefined ? { reason: input.reason } : { reason: input.reason, detail: input.detail };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function describe(value: unknown): string {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(value) ?? String(value);
|
|
50
|
+
} catch {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The actor logic that throws. Unbranded: what `workspace()` invokes to send a caught Failure up
|
|
56
|
+
* again, which is the wrapper's mechanism and no state of the author's that fails the run. */
|
|
57
|
+
export const failLogic: PromiseActorLogic<never, FailureInput> = fromPromise<never, FailureInput>(async ({ input }) => {
|
|
58
|
+
throw failure(checked(input));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** The logic `fail()` returns: {@link failLogic} with the brand the walk reads. */
|
|
62
|
+
export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL_SLOT]: true };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fail the run: the Part a Machine invokes to end work that did not succeed. Put it in `actors`
|
|
66
|
+
* under a name, and invoke it from any state, at any depth: `actors: { fail: fail() }`, then
|
|
67
|
+
* `invoke: { src: "fail", input: { reason } }`.
|
|
68
|
+
*
|
|
69
|
+
* The input is `{ reason: string; detail?: unknown }`. `reason` is text for a person, and it must
|
|
70
|
+
* not be empty. `detail` is optional JSON for a machine reader.
|
|
71
|
+
*
|
|
72
|
+
* The Failure goes up from the state that invokes `fail`, through each parent Machine, as an error
|
|
73
|
+
* does. An `onError` on the way catches it; {@link isFailure} tells it from a defect. When no state
|
|
74
|
+
* catches it, the run ends `failed`: its output is `{ reason, detail }`, and `jr2 run` exits 1. A
|
|
75
|
+
* `workspace()` that a Failure goes through deletes its Sandbox first.
|
|
76
|
+
*
|
|
77
|
+
* To fail, invoke `fail`. Do not throw from a promise to fail: a throw is a defect, and the run
|
|
78
|
+
* ends `error`. A final state means `done`: do not use one for work that did not succeed. To keep
|
|
79
|
+
* the work for a person to read, wait at a Gate before the state that fails.
|
|
80
|
+
*
|
|
81
|
+
* It refuses an input that is not a Failure's, and the run ends `error`: ``fail: the input must be
|
|
82
|
+
* { reason: string; detail?: unknown } with a reason that is not empty (got <value>) — invoke:
|
|
83
|
+
* { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)``.
|
|
84
|
+
*
|
|
85
|
+
* @returns The actor logic of the slot.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* import { z } from "zod";
|
|
90
|
+
* import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
91
|
+
*
|
|
92
|
+
* const runInput = z.object({ branch: z.string() });
|
|
93
|
+
* type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
94
|
+
*
|
|
95
|
+
* const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
|
|
96
|
+
*
|
|
97
|
+
* const body = jr2Setup({
|
|
98
|
+
* types: {} as { input: BodyInput; context: BodyInput },
|
|
99
|
+
* actors: {
|
|
100
|
+
* cut: worktree(),
|
|
101
|
+
* tests: command(["npm", "test"], { timeout: "15m" }),
|
|
102
|
+
* fail: fail(),
|
|
103
|
+
* },
|
|
104
|
+
* }).createMachine({
|
|
105
|
+
* id: "check",
|
|
106
|
+
* context: ({ input }) => input,
|
|
107
|
+
* on: { "workspace.lost": ".lost" },
|
|
108
|
+
* initial: "cutting",
|
|
109
|
+
* states: {
|
|
110
|
+
* cutting: {
|
|
111
|
+
* invoke: {
|
|
112
|
+
* src: "cut",
|
|
113
|
+
* input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
|
|
114
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
|
|
115
|
+
* },
|
|
116
|
+
* },
|
|
117
|
+
* testing: {
|
|
118
|
+
* invoke: {
|
|
119
|
+
* src: "tests",
|
|
120
|
+
* input: ({ context }) => ({ worktree: testTree(context) }),
|
|
121
|
+
* onDone: [
|
|
122
|
+
* {
|
|
123
|
+
* guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
|
|
124
|
+
* target: "passed",
|
|
125
|
+
* },
|
|
126
|
+
* { target: "failing" },
|
|
127
|
+
* ],
|
|
128
|
+
* },
|
|
129
|
+
* },
|
|
130
|
+
* // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
131
|
+
* failing: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
|
|
132
|
+
* passed: { type: "final" },
|
|
133
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
134
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
135
|
+
* },
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* export const machine = workspace(body, {
|
|
139
|
+
* input: runInput,
|
|
140
|
+
* repos: { app: "https://github.com/you/app.git" },
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
|
|
145
|
+
*/
|
|
146
|
+
export function fail(): FailSlot {
|
|
147
|
+
// Logic of its own per slot, so the brand is on this slot's logic and on no other's.
|
|
148
|
+
const logic = fromPromise<never, FailureInput>(async ({ input }) => {
|
|
149
|
+
throw failure(checked(input));
|
|
150
|
+
});
|
|
151
|
+
return Object.defineProperty(logic, FAIL_SLOT, { value: true }) as FailSlot;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Is this actor logic a `fail()` slot? A test on the brand, like `isAgent`: what the walk reads to
|
|
155
|
+
* list each state that can fail the run (ADR-0073). */
|
|
156
|
+
export function isFailSlot(logic: unknown): logic is FailSlot {
|
|
157
|
+
return typeof logic === "object" && logic !== null && (logic as { [FAIL_SLOT]?: unknown })[FAIL_SLOT] === true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Is this error a Failure, which a state invoked `fail` to make? A defect — any other error — is
|
|
162
|
+
* not. Use it in the guard of an `onError` to catch a Failure and let a defect go up, and read the
|
|
163
|
+
* Failure's `reason` and `detail` from the error it narrows.
|
|
164
|
+
*
|
|
165
|
+
* @param error - The error of an error event: `event.error` in an `onError`.
|
|
166
|
+
* @returns True for a Failure, with `error` narrowed to `{ reason, detail? }`.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* import { z } from "zod";
|
|
171
|
+
* import { defineEvent, fail, isFailure, jr2Setup } from "@jr2/orchestrator";
|
|
172
|
+
*
|
|
173
|
+
* const retry = defineEvent({ name: "retry", description: "Try the plan again.", input: z.object({}) });
|
|
174
|
+
*
|
|
175
|
+
* const attempt = jr2Setup({ actors: { fail: fail() } }).createMachine({
|
|
176
|
+
* id: "attempt",
|
|
177
|
+
* initial: "stuck",
|
|
178
|
+
* states: { stuck: { invoke: { src: "fail", input: { reason: "no plan fits the ticket" } } } },
|
|
179
|
+
* });
|
|
180
|
+
*
|
|
181
|
+
* export const machine = jr2Setup({ events: [retry], actors: { attempt } }).createMachine({
|
|
182
|
+
* id: "planner",
|
|
183
|
+
* initial: "planning",
|
|
184
|
+
* states: {
|
|
185
|
+
* planning: {
|
|
186
|
+
* invoke: {
|
|
187
|
+
* src: "attempt",
|
|
188
|
+
* onDone: "planned",
|
|
189
|
+
* // A Failure waits for a person; a defect has no route, so it goes up and the run ends `error`.
|
|
190
|
+
* onError: { guard: ({ event }) => isFailure(event.error), target: "asking" },
|
|
191
|
+
* },
|
|
192
|
+
* },
|
|
193
|
+
* asking: { invoke: { src: "gate" }, on: { retry: "planning" } },
|
|
194
|
+
* planned: { type: "final" },
|
|
195
|
+
* },
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
|
|
200
|
+
*/
|
|
201
|
+
export function isFailure(error: unknown): error is Failure {
|
|
202
|
+
return (
|
|
203
|
+
typeof error === "object" &&
|
|
204
|
+
error !== null &&
|
|
205
|
+
(error as { [FAILURE]?: unknown })[FAILURE] === true &&
|
|
206
|
+
typeof (error as { reason?: unknown }).reason === "string"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The name of the guard that every Chart has (ADR-0070, ADR-0073): {@link isFailure} on the
|
|
211
|
+
* event's `error`, so a Chart's `onError` can tell a Failure from a defect. It is a predicate and
|
|
212
|
+
* gives no capability, so it is never lent, as `jr2.sentBy` is not. */
|
|
213
|
+
export const FAILURE_GUARD = "jr2.failure";
|
|
214
|
+
|
|
215
|
+
/** The implementation of {@link FAILURE_GUARD}: true when the event's `error` is a Failure. */
|
|
216
|
+
export const failureGuard = ({ event }: { event: { error?: unknown } }): boolean => isFailure(event.error);
|
|
217
|
+
|
|
218
|
+
/** A Failure's payload as plain JSON data: what a failed run's output is, and what `workspace()`
|
|
219
|
+
* keeps to send it up again. */
|
|
220
|
+
export function failurePayload(error: Failure): FailureInput {
|
|
221
|
+
return error.detail === undefined ? { reason: error.reason } : { reason: error.reason, detail: error.detail };
|
|
222
|
+
}
|